From 43b5cfa94eb17eb07e205222cae99fd79dd6116f Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 24 Sep 2026 22:21:09 +0000 Subject: [PATCH] [Docs] (Disaggregated): the four index documents keep only summaries, and every phase's detail lives in its own notes//README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ROADMAP, MEASUREMENTS and ARCHITECTURE are rewritten as summaries (333 KB of index text -> 84 KB). ROADMAP's phase table is one line per phase plus a link; MEASUREMENTS keeps headline numbers under its old §1-§13 numbering (plus §14-§16 for P6.5, P7 and P12); ARCHITECTURE keeps every decided rule under the same §1-§17 / appendix numbering, so the `ARCHITECTURE.md §N` references in code still land, and §17 becomes an index of the phase landing shapes. - CURRENT_STAGE_PROGRESS now describes the current stage (the P12 on-screen-server subset) and absorbs CURRENT_STAGE_STATUS, which is removed. The P7 status tables, the P6 closing snapshot and the P6.5 first-wave snapshot move to notes/p7, notes/p6 and notes/p65. - Each phase gets notes//README.md: a short summary, then the verbatim material that left the index documents (its ROADMAP row, its MEASUREMENTS section with subsection numbers kept, its ARCHITECTURE §17 part, milestones) and a file index. P8-P11 and P13 get their planned scope; the full open-question list moves to notes/OPEN-QUESTIONS.md. - The phase reports that sat at the top level move into their phase folders with unchanged names: P5D-INPROC-PERFORMANCE, P5E-RUNAHEAD, P5F-WIRE-COMPLETENESS, P6-SPAWN-PLAN, P6-CONTRACT-DRAFT, P6-ENDSTATE-REVIEW. Their relative links, CONTRACT-P6.md's links to them and one tool docstring are updated; notes/README.md maps every old path and section number to its new place. - 71 files under notes/recovered/ that were byte-identical copies of phase-folder files are removed. - Corrections made while condensing: the server process reads Transport as Spawn (CONTRACT-P6 §3.1 amended the old "child forces monolith" rule); both backends now publish kCapRunAheadApply; MOBILEGL_IPC_RESPAWN is a named refusal and MOBILEGL_IPC_IDLE_EXIT_S is not parsed; the Redmi pin row is in tools/device_bench/pin_device.sh. check_doc_citations over docs/Disaggregated/*.md: 0 problems. No relative link is newly broken. --- MobileGL/MG_Remote/CONTRACT-P6.md | 6 +- docs/Disaggregated/ARCHITECTURE.md | 673 +++++-------- docs/Disaggregated/CURRENT_STAGE_PROGRESS.md | 277 +----- docs/Disaggregated/CURRENT_STAGE_STATUS.md | 85 -- docs/Disaggregated/MEASUREMENTS.md | 909 ++---------------- docs/Disaggregated/README.md | 75 +- docs/Disaggregated/ROADMAP.md | 337 ++----- .../devices/pin-verification-2026-09-07.md | 2 +- docs/Disaggregated/notes/OPEN-QUESTIONS.md | 30 + docs/Disaggregated/notes/README.md | 83 +- docs/Disaggregated/notes/p0/README.md | 94 ++ docs/Disaggregated/notes/p05/README.md | 24 + docs/Disaggregated/notes/p1/README.md | 57 ++ docs/Disaggregated/notes/p10/README.md | 15 + docs/Disaggregated/notes/p11/README.md | 15 + docs/Disaggregated/notes/p12/PLAN-P12.md | 2 +- docs/Disaggregated/notes/p12/README.md | 44 + docs/Disaggregated/notes/p13/README.md | 15 + docs/Disaggregated/notes/p2/README.md | 95 ++ docs/Disaggregated/notes/p34b/README.md | 82 ++ docs/Disaggregated/notes/p3a/README.md | 117 +++ docs/Disaggregated/notes/p4a/README.md | 95 ++ docs/Disaggregated/notes/p5/README.md | 135 +++ docs/Disaggregated/notes/p5b/README.md | 150 +++ docs/Disaggregated/notes/p5c/README.md | 114 +++ .../{ => notes/p5d}/P5D-INPROC-PERFORMANCE.md | 0 docs/Disaggregated/notes/p5d/README.md | 54 ++ .../{ => notes/p5e}/P5E-RUNAHEAD.md | 0 docs/Disaggregated/notes/p5e/README.md | 193 ++++ .../{ => notes/p5f}/P5F-WIRE-COMPLETENESS.md | 10 +- docs/Disaggregated/notes/p5f/README.md | 61 ++ .../{ => notes/p6}/P6-CONTRACT-DRAFT.md | 6 +- .../{ => notes/p6}/P6-ENDSTATE-REVIEW.md | 4 +- .../{ => notes/p6}/P6-SPAWN-PLAN.md | 4 +- docs/Disaggregated/notes/p6/README.md | 378 ++++++++ docs/Disaggregated/notes/p65/README.md | 187 ++++ docs/Disaggregated/notes/p7/README.md | 120 +++ docs/Disaggregated/notes/p8/README.md | 15 + docs/Disaggregated/notes/p9/README.md | 15 + .../notes/recovered/wf2/BRIEF-DOCS.md | 26 - .../notes/recovered/wf2/BRIEF-P0.md | 48 - .../notes/recovered/wf2/p0-device-findings.md | 30 - .../notes/recovered/wf3/BRIEF-P05.md | 434 --------- .../wf3/p05-results/include-gate-review-v1.md | 95 -- .../wf3/p05-results/include-gate-v1.md | 280 ------ .../program-artifacts-review-v1.md | 114 --- .../wf3/p05-results/program-artifacts-v1.md | 66 -- .../wf3/p05-results/value-types-review-v1.md | 83 -- .../wf3/p05-results/value-types-v1.md | 63 -- .../notes/recovered/wf4/BRIEF-P1.md | 572 ----------- .../notes/recovered/wf4/P1-LANE-FINDINGS.md | 65 -- .../recovered/wf4/p1-results/core-contract.md | 73 -- .../wf4/p1-results/core-review-v1.md | 103 -- .../wf4/p1-results/core-review-v2.md | 95 -- .../wf4/p1-results/core-review-v3.md | 324 ------- .../notes/recovered/wf4/p1-results/core-v1.md | 82 -- .../notes/recovered/wf4/p1-results/core-v2.md | 80 -- .../notes/recovered/wf4/p1-results/core-v3.md | 62 -- .../wf4/p1-results/espryt-review-v1.md | 70 -- .../recovered/wf4/p1-results/espryt-v1.md | 79 -- .../wf4/p1-results/fix-core-review-v1.md | 260 ----- .../recovered/wf4/p1-results/fix-core-v1.md | 284 ------ .../wf4/p1-results/fix-tests-review-v1.md | 272 ------ .../recovered/wf4/p1-results/fix-tests-v1.md | 214 ----- .../wf4/p1-results/magma-review-v1.md | 87 -- .../recovered/wf4/p1-results/magma-v1.md | 72 -- .../wf4/p1-results/verify-review-v1.md | 97 -- .../wf4/p1-results/verify-review-v2.md | 69 -- .../recovered/wf4/p1-results/verify-v1.md | 84 -- .../recovered/wf4/p1-results/verify-v2.md | 202 ---- .../recovered/wf5/p2-results/contract.md | 229 ----- .../wf5/p2-results/espryt-review-v1.md | 402 -------- .../wf5/p2-results/espryt-review-v2.md | 274 ------ .../wf5/p2-results/espryt-review-v3.md | 363 ------- .../wf5/p2-results/espryt-review-v4.md | 358 ------- .../recovered/wf5/p2-results/espryt-v1.md | 268 ------ .../recovered/wf5/p2-results/espryt-v2.md | 360 ------- .../recovered/wf5/p2-results/espryt-v3.md | 446 --------- .../recovered/wf5/p2-results/espryt-v4.md | 474 --------- .../wf5/p2-results/gates-review-v1.md | 277 ------ .../wf5/p2-results/gates-review-v2.md | 300 ------ .../wf5/p2-results/gates-review-v3.md | 267 ----- .../recovered/wf5/p2-results/gates-v1.md | 370 ------- .../recovered/wf5/p2-results/gates-v2.md | 357 ------- .../recovered/wf5/p2-results/gates-v3.md | 352 ------- .../wf5/p2-results/magma-review-v1.md | 311 ------ .../wf5/p2-results/magma-review-v2.md | 332 ------- .../wf5/p2-results/magma-review-v3.md | 303 ------ .../recovered/wf5/p2-results/magma-v1.md | 257 ----- .../recovered/wf5/p2-results/magma-v2.md | 316 ------ .../recovered/wf5/p2-results/magma-v3.md | 372 ------- .../wf5/p2-results/spans-review-v1.md | 334 ------- .../wf5/p2-results/spans-review-v2.md | 347 ------- .../wf5/p2-results/spans-review-v3.md | 252 ----- .../recovered/wf5/p2-results/spans-v1.md | 224 ----- .../recovered/wf5/p2-results/spans-v2.md | 288 ------ .../recovered/wf5/p2-results/spans-v3.md | 273 ------ .../wf5/p2-results/tracker-review-v1.md | 358 ------- .../wf5/p2-results/tracker-review-v2.md | 402 -------- .../wf5/p2-results/tracker-review-v3.md | 405 -------- .../wf5/p2-results/tracker-review-v4.md | 329 ------- .../recovered/wf5/p2-results/tracker-v1.md | 270 ------ .../recovered/wf5/p2-results/tracker-v2.md | 334 ------- .../recovered/wf5/p2-results/tracker-v3.md | 376 -------- .../recovered/wf5/p2-results/tracker-v4.md | 345 ------- .../notes/recovered/wf5/scout-docs-spec.md | 432 --------- .../recovered/wf5/scout-measure-infra.md | 528 ---------- .../notes/recovered/wf5/scout-render-state.md | 810 ---------------- .../notes/recovered/wf5/scout-track-h.md | 320 ------ .../notes/recovered/wf5/scout-tracker.md | 397 -------- tools/device_bench/p6/ab_session.py | 4 +- 111 files changed, 2645 insertions(+), 20434 deletions(-) delete mode 100644 docs/Disaggregated/CURRENT_STAGE_STATUS.md create mode 100644 docs/Disaggregated/notes/OPEN-QUESTIONS.md create mode 100644 docs/Disaggregated/notes/p0/README.md create mode 100644 docs/Disaggregated/notes/p05/README.md create mode 100644 docs/Disaggregated/notes/p1/README.md create mode 100644 docs/Disaggregated/notes/p10/README.md create mode 100644 docs/Disaggregated/notes/p11/README.md create mode 100644 docs/Disaggregated/notes/p12/README.md create mode 100644 docs/Disaggregated/notes/p13/README.md create mode 100644 docs/Disaggregated/notes/p2/README.md create mode 100644 docs/Disaggregated/notes/p34b/README.md create mode 100644 docs/Disaggregated/notes/p3a/README.md create mode 100644 docs/Disaggregated/notes/p4a/README.md create mode 100644 docs/Disaggregated/notes/p5/README.md create mode 100644 docs/Disaggregated/notes/p5b/README.md create mode 100644 docs/Disaggregated/notes/p5c/README.md rename docs/Disaggregated/{ => notes/p5d}/P5D-INPROC-PERFORMANCE.md (100%) create mode 100644 docs/Disaggregated/notes/p5d/README.md rename docs/Disaggregated/{ => notes/p5e}/P5E-RUNAHEAD.md (100%) create mode 100644 docs/Disaggregated/notes/p5e/README.md rename docs/Disaggregated/{ => notes/p5f}/P5F-WIRE-COMPLETENESS.md (97%) create mode 100644 docs/Disaggregated/notes/p5f/README.md rename docs/Disaggregated/{ => notes/p6}/P6-CONTRACT-DRAFT.md (98%) rename docs/Disaggregated/{ => notes/p6}/P6-ENDSTATE-REVIEW.md (99%) rename docs/Disaggregated/{ => notes/p6}/P6-SPAWN-PLAN.md (98%) create mode 100644 docs/Disaggregated/notes/p6/README.md create mode 100644 docs/Disaggregated/notes/p65/README.md create mode 100644 docs/Disaggregated/notes/p7/README.md create mode 100644 docs/Disaggregated/notes/p8/README.md create mode 100644 docs/Disaggregated/notes/p9/README.md delete mode 100644 docs/Disaggregated/notes/recovered/wf2/BRIEF-DOCS.md delete mode 100644 docs/Disaggregated/notes/recovered/wf2/BRIEF-P0.md delete mode 100644 docs/Disaggregated/notes/recovered/wf2/p0-device-findings.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/BRIEF-P05.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/BRIEF-P1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/P1-LANE-FINDINGS.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-contract.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/core-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/fix-tests-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/fix-tests-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/magma-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/magma-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/verify-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/verify-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/verify-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf4/p1-results/verify-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/contract.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-review-v4.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/espryt-v4.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/gates-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/magma-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/spans-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-review-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-review-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-review-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-review-v4.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-v1.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-v2.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-v3.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/p2-results/tracker-v4.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/scout-docs-spec.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/scout-measure-infra.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/scout-render-state.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/scout-track-h.md delete mode 100644 docs/Disaggregated/notes/recovered/wf5/scout-tracker.md diff --git a/MobileGL/MG_Remote/CONTRACT-P6.md b/MobileGL/MG_Remote/CONTRACT-P6.md index 78bf8841..b89b47ea 100644 --- a/MobileGL/MG_Remote/CONTRACT-P6.md +++ b/MobileGL/MG_Remote/CONTRACT-P6.md @@ -14,10 +14,10 @@ integrator; the packages compile against the rows below from day one. **What this file is written from.** [`a6-audit-v1.md`](../../docs/Disaggregated/notes/p6/a6-audit-v1.md) (198 rows, seven items, each adversarially re-resolved) and [`a6-link-experiment.md`](../../docs/Disaggregated/notes/p6/a6-link-experiment.md) (the 184 symbols). -It supersedes [`P6-CONTRACT-DRAFT.md`](../../docs/Disaggregated/P6-CONTRACT-DRAFT.md), which was +It supersedes [`P6-CONTRACT-DRAFT.md`](../../docs/Disaggregated/notes/p6/P6-CONTRACT-DRAFT.md), which was written before its own audit and is **wrong in four places** (§10.1). The re-scoping against the eventual client-in-a-VM end state is -[`P6-ENDSTATE-REVIEW.md`](../../docs/Disaggregated/P6-ENDSTATE-REVIEW.md); this file takes from it +[`P6-ENDSTATE-REVIEW.md`](../../docs/Disaggregated/notes/p6/P6-ENDSTATE-REVIEW.md); this file takes from it only what P6 must not preclude, and §11 says what P6 explicitly does not take on. --- @@ -631,7 +631,7 @@ Negative controls, each run red once: ### 10.3 Amendments of 2026-09-22 — the end state re-ruled 1. **The end state is TCP across machines, OSes and architectures**, not an AVF pVM over - `AF_VSOCK`. `docs/Disaggregated/P6-ENDSTATE-REVIEW.md` carries the retraction at its head; its + `AF_VSOCK`. `docs/Disaggregated/notes/p6/P6-ENDSTATE-REVIEW.md` carries the retraction at its head; its §3 gains form **D**. Same-machine `spawn` (AF_UNIX + shared segments) stays as the local form. 2. **The transport stack has two independent axes.** The control plane (`ITransport`: `fork` inherited fds / `unix:` / `tcp://host:port`) and the data plane (`ILink`: `ShmLink` / diff --git a/docs/Disaggregated/ARCHITECTURE.md b/docs/Disaggregated/ARCHITECTURE.md index 78645ea6..080c10b4 100644 --- a/docs/Disaggregated/ARCHITECTURE.md +++ b/docs/Disaggregated/ARCHITECTURE.md @@ -1,315 +1,215 @@ # MGPipe 设计与架构 -> 本文只写**已决定**的设计,每条决定附一行理由。落地状态:P0–P5c 已落地(代码头 `b88e8487`);标 **P6+** 的是后续形状,阶段号见 `ROADMAP.md`;实测数字见 `MEASUREMENTS.md`;P5 / P5b / P5c 的 wire 契约原文在 `MobileGL/MG_Remote/CONTRACT-P5.md`、`CONTRACT-P5B.md`、`CONTRACT-P5C.md`。 +> 只写**已决定**的设计,每条附一句理由。章节编号稳定(代码注释按 `ARCHITECTURE.md §N` 引用它们)。阶段号与状态见 [`ROADMAP.md`](ROADMAP.md),实测见 [`MEASUREMENTS.md`](MEASUREMENTS.md);各阶段的落地细节、偏差与当时的论证在 `notes/<阶段>/README.md`(§17 是索引)。wire 契约原文在 `MobileGL/MG_Remote/CONTRACT-*.md`,与本文冲突时以更新的契约为准。 ## 1. 边界 ### 1.1 一句话 -`MG_Backend` 已经是一台贴着目标 API 的状态机(Espryt:逐字节渲染状态镜像、twin registry、三条 persistent ring;Magma:`SetupDrawSnapshot`、pipeline memo、五个 `Vk*Manager`)。它缺的不是状态,而是一份"我被告知了什么"的显式声明。MGPipe 就是那份声明:前端在每条 verb 之前把变化**推**过去,后端不再拉 `MG_State::pGLContext`;server 进程因此只装 `MG_Backend` + MGPipe 对象表,不链接 `MG_State`、`MG_Impl`、glslang。 - -接口是从两个后端自己维护的关键结构反推出来的(`SetupDrawSnapshot` 字段并集 → `set_*`;`DrawTextureSyncKeys` → sampler view 三件;`ResolvedDrawBuffers` / `ResolvedVertexBindings` → vertex elements 三件;`g_syncedRenderStateParameters` → render-state CSO;`UnpackStagingBlock` → `MGPSubData` 的 region 形状;`BufferBackendOps` 七个 hook → `resource_*` 全族)。gallium 是目的地(词汇可读、可迁移),不是推导前提。 +`MG_Backend` 本来就是一台贴着目标 API 的状态机(Espryt = DirectGLES,Magma = DirectVulkan),缺的是一份"我被告知了什么"的显式声明。MGPipe 就是这份声明:前端在每条 verb 之前把变化**推**过去,后端不再拉 `MG_State::pGLContext`。接口从两个后端自己维护的关键结构反推(`SetupDrawSnapshot`、`DrawTextureSyncKeys`、`ResolvedVertexBindings`、`g_syncedRenderStateParameters`、`BufferBackendOps` …);gallium 是词汇的目的地,不是推导前提。 ### 1.2 两张函数指针表 -`MGPipeScreen`(share-group 作用域:caps、resource、persistent map、fence)与 `MGPipeContext`(其余全部),由 `PipeCalls.def` 经 G1 生成(`MG_Pipe/generated/PipeTables.inc`)。 +`MGPipeScreen`(share-group 作用域:caps、resource、persistent map、fence)与 `MGPipeContext`(其余全部),由 `PipeCalls.def` 生成。 -- 函数指针 struct 而非虚基类:边界今天就是函数指针 struct(`gBackendFunctionsTable`),**null 项已经表示"未实现,前端回退"**,`MG_Test` 已用替换整张表的方式 mock 后端。 -- 两张表从第一天分开:事后拆分意味着给记录重新编号。v1 只有一个 screen、一个 context、一条 flow。 -- EGL 生命周期 8 项与 caps 面留在 `pActiveBackendObject` 的虚函数上(罕见路径)。 +- 函数指针 struct 而非虚基类:null 项已表示"未实现,前端回退",`MG_Test` 整表替换即可 mock。 +- 两张表从第一天分开,事后拆会给记录重新编号;v1 只有一个 screen、一个 context、一条 flow。 +- EGL 生命周期与 caps 面留在 `pActiveBackendObject` 的虚函数上(罕见路径,走控制面)。 ### 1.3 三种形态,一份后端 | 形态 | 表里装的是什么 | 用途 | |---|---|---| -| `monolith`(默认) | backend 自己的函数;回调直调 `MG_State`;`MGHostSpan.Ptr` 指向 client shadow | 出货 | -| `inproc` | 发射器 → 同进程第二个线程上的 applier | CI 形态;同时就是 monolith 的**渲染线程**(把 `PrepareForDraw` 与驱动调用搬离 GL 线程) | -| `spawn`(P6) | 发射器 → SPSC shm ring → 另一个进程的 applier → 同一批 backend 函数 | 两进程出货形态 | +| `monolith`(默认) | 后端自己的函数,回调直调 `MG_State` | 出货 | +| `inproc` | 发射器 → 同进程第二个线程上的 applier | CI 主验收;也就是 monolith 的**渲染线程** | +| `spawn` | 发射器 → 数据面 → 另一个进程的 applier → 同一批后端函数 | 两进程形态;控制面可以是 `fork` / `unix:` / `tcp://`(§11.9),server 可在另一台机器上 | -唯一 hook 点是 `MG_Backend::Init()` 里一个 `#if MOBILEGL_BUILD_DISAGGREGATED` 分支:`MG_Config::Transport != Monolith` 时装 `MG_Remote::BackendObject_Remote`,否则走今天的 `switch`。`MG_Impl` 的边界调用点零 `#ifdef`。 +唯一 hook 点是 `MG_Backend::Init()` 里一个 `#if MOBILEGL_BUILD_DISAGGREGATED` 分支:`MG_Config::Transport != Monolith` 时装 `MG_Remote::BackendObject_Remote`。`MG_Impl` 的边界调用点零 `#ifdef`。 ## 2. 对象模型 ### 2.1 句柄 = `{slot, gen}`(`MG_Pipe/MGPipeHandles.h`) -- 8 字节 POD,按值走寄存器对;**client 铸造,server 永不返回句柄** → 整份目录零创建 round trip(对 gallium 的偏离 D1)。 -- slot 稠密、按 kind 分配(free list + 高水位),server 对象表是数组不是哈希表;`IndexGenerator` 的 LIFO 名字复用正是句柄要关掉的问题。 -- `gen` 只在 slot 复用时 ++,不在 respecify 时 ++;同一 slot 复用 2³² 次才回绕,debug 分配器断言回绕。 -- kind:`Buffer, Texture, Renderbuffer, Framebuffer, Xfb, RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso, Fence, Query, Context`。 -- 保留句柄:`{0,0}` = null;`{0,1}` of `Framebuffer` = 默认帧缓冲(退役 Espryt 的 `defaultFBO` 身份比较);`ShaderCso` slot 空间的高段保留给 program pipeline 合成体(`kMGPipeShaderCsoCompositeSlotBase`)。 -- GL name 只以 `GlNameForDiag` 出现在 `MGPResourceDesc` 里,永不做身份、永不进 memo 键或 content hash;`GetLifetimeId()` 留在 client 作 tracker 自己的身份,client 维护 `lifetimeId → slot`。 +- 8 字节 POD;**client 铸造,server 永不返回句柄** → 整份目录零创建 round trip(对 gallium 的偏离 D1)。 +- slot 按 kind 稠密分配(free list + 高水位),server 对象表是数组;`gen` 只在 slot 复用时 ++。 +- kind:`Buffer, Texture, Renderbuffer, Framebuffer, Xfb, RenderStateCso, VertexElementsCso, SamplerCso, SamplerViewCso, ShaderCso, Fence, Query, Context`。保留:`{0,0}` = null;`Framebuffer {0,1}` = 默认帧缓冲;`ShaderCso` 高段给 program pipeline 合成体。 +- GL name 只作诊断(`GlNameForDiag`),永不做身份、memo 键或 content hash。 ### 2.2 两种世代,严格分开 | | 拥有者 | 回答 | 过线 | |---|---|---|---| | `MGPipeHandle::Gen` | client | "还是同一个 GL 对象吗?" | 是 | -| `MGGen`(`g_bufferMutationEpoch`、`m_textureImageEpoch` 等后端纪元) | server | "我自己是否重铸了驱动对象?" | **永不**;server→client 只以纹理拉取请求出现(§8.4) | +| `MGGen`(后端纪元,如 `g_bufferMutationEpoch`) | server | "我自己是否重铸了驱动对象?" | **永不**(只以纹理拉取请求的形式出现,§8.4) | -规范:任何 MGPipe 调用不得要求 client 提供或知晓 `MGGen`;client 的回绕 `Uint16` 版本计数器永远不是新鲜度的唯一证明——过线时要么加宽、要么与 `{slot, gen}` 同行。 +client 的回绕 `Uint16` 版本计数器永远不是新鲜度的唯一证明:过线时要么加宽,要么与 `{slot, gen}` 同行。 ### 2.3 CSO 与可变对象 -| 类别 | 形态 | 对应后端已有缓存 | -|---|---|---| -| `VertexElementsCso` | create/bind/delete | `VertexInputStateFactory::m_cache`(Magma);Espryt 逐 VAO twin | -| `SamplerCso` | create/delete + `bind_sampler_states` | `VkSamplerManager`、`BackendSamplerObject` | -| `SamplerViewCso` | create/delete + `set_sampler_views` | `TextureResource::perMipViews`、`SyncTextureViewToBackend` | -| `ShaderCso` | create/bind/delete + server 侧惰性特化 | `ProgramFactory::m_cache`、`BackendProgramObjectImpl` | -| `RenderStateCso` | create/bind/delete,身份 = pipeline 子集 | Espryt 值镜像;Magma `ComputePipelineStateHash` | -| Buffer / Texture / Renderbuffer | create / respecify / subdata / destroy | 各自 twin | -| Framebuffer / Xfb | per-context 身份 + `set_*` payload | `BackendFramebufferObject`、`m_xfbCounterSlotByObject` | - -CSO 在 client 侧内容寻址(Mesa `cso_cache` 先例):每类一张 `flat_hash_map`,LRU 淘汰时发 `delete_*`;render-state 容量 64(`kMGPipeCsoCacheCapacity`,P13 重调)、sampler state 256。两条已落地的偏离: - -- **D-G1(P3a)**:Espryt 的 vertex-elements CSO **按 VAO 身份寻址**(一 VAO 一句柄,配置变化在同一句柄上重发)——Espryt 的 twin 持有驱动 VAO 名与 scratch buffer,两个格式相同的 VAO 不能共享;1024 项的内容寻址是 P7(Magma)在同一组调用之上加的。 -- **D-F2(P4a)**:Espryt 的 sampler **view** 按纹理对象身份寻址(Espryt 没有可共享的驱动侧视图对象);sampler **state** 内容寻址并带**引用计数**——`MGPTextureParams::BuiltinSampler` 指着的项不允许被 LRU 挤掉(超容铸造计入 `OverCapacityMints`),否则一条标准记录会指向已换代的句柄。 +- CSO(render state、vertex elements、sampler、sampler view、shader)在 **client 侧内容寻址**(Mesa `cso_cache` 先例),LRU 淘汰时发 `delete_*`;render-state 容量 64(P13 重调)、sampler state 256。 +- Buffer / Texture / Renderbuffer 走 create / respecify / subdata / destroy;Framebuffer / Xfb 是 per-context 身份 + `set_*` payload。 +- 两条偏离:**D-G1** Espryt 的 vertex-elements 按 VAO 身份寻址(twin 持驱动 VAO 名,不能共享);**D-F2** Espryt 的 sampler view 按纹理身份寻址,sampler state 内容寻址且带引用计数(被 `BuiltinSampler` 指着的项不许被 LRU 挤掉)。 ## 3. 调用目录 ### 3.1 单一真相源 -`MobileGL/MG_Pipe/PipeCalls.def`:一行一个调用 `X(Name, PayloadStruct, Class, Flags)`。**线上 opcode 就是行的位置**(1-based),新调用只能**追加**、退役的调用保留槽位。`MGP_CALL_LIST_DOCUMENTED_COUNT = 76`(P5b 契约追加了 5 条 verb),由 `MG_Test/Pipe/PipeCatalogueTest.cpp` 钉住。 - -生成器(产物提交进树,CI `pipe-gates` 重生成并 `git diff --exit-code`): +`MobileGL/MG_Pipe/PipeCalls.def`:一行一个 `X(Name, Payload, Class, Flags, WaitClass)`。**线上 opcode 就是行序**,只能追加、退役保留槽位;行数由 `MGP_CALL_LIST_DOCUMENTED_COUNT`(今天 81)与 `PipeCatalogueTest` 钉住。生成器产物提交进树,CI `pipe-gates` 重生成并 `git diff --exit-code`: | | 产物 | 内容 | |---|---|---| | G1 | `PipeTables.inc` | 两张函数指针表 | -| G2 | `PipeThunks.inc` | monolith 直调 thunk `MGP_()`,`MG_Impl` 的边界站点逐名改到它上面 | -| G3 | `PipeWire.inc` | wire 记录 + 每种一条尺寸 `static_assert` + applier 分发前的运行期边界检查 → `Fatal{ProtocolCorruption}` | -| G4 | `PipeVerify.inc` | `MOBILEGL_PIPE_VERIFY` 的逐字段比对器(字段表 `PipeFields.def`;浮点按位比较) | -| G5 | `PipeFilled.inc` | `PipeInputs` 字段 id(63 个,`static_assert`)与逐 verb 世代 poison | -| G6 | `PipeCoverage.inc` | 后端读点清单 → MGPipe 调用的映射(`Coverage.def`),**0 UNMAPPED** 是门 | -| G7 | `PipeSpanTable.inc` | render-state pipeline 子集的成员名表;chunk 表与 setter 一致性测试在 `RenderStateSpansTest` | -| G8 | `PipeFieldOwnership.inc`(`gen_pipe_field_ownership.py`) | 每个 `PipeInputs` 字段恰好属于 `RECORD-SUPPLIED / APPLIER-DERIVED / BARRIER-PULLED / FATAL` 之一,不在任何一类 = 构建失败;目前 33 / 2 / 36 / 4。从 `MG_Backend/MGPipe/PipeInputs.h` include,**不从 `MG_Pipe/MGPipe.h`**(后者在 pull 构建的 include 闭包里,G1 不允许符号位移) | +| G2 | `PipeThunks.inc` | monolith 直调 thunk | +| G3 | `PipeWire.inc` | wire 记录、尺寸 `static_assert`、applier 边界检查 | +| G4 | `PipeVerify.inc` | `MOBILEGL_PIPE_VERIFY` 逐字段比对器(`PipeFields.def`) | +| G5 | `PipeFilled.inc` | `PipeInputs` 字段 id 与逐 verb 世代 poison | +| G6 | `PipeCoverage.inc` | 后端读点 → 调用映射(`Coverage.def`),0 UNMAPPED 是门 | +| G7 | `PipeSpanTable.inc` | render-state pipeline 子集成员表 | +| G8 | `PipeFieldOwnership.inc` | 每个 `PipeInputs` 字段恰属 `RECORD-SUPPLIED / APPLIER-DERIVED / BARRIER-PULLED / FATAL` 之一(P5f 起 0 BARRIER-PULLED) | -另有 `PipeFillPoints.inc`(`FillPoints.def`,83 条 `MGP_FILL` 覆盖 69 个 verb)与 `DirtySurface.def`(`gen_pipe_dirty_surface.py`,§5.2)。 +另有 `FillPoints.def`(填充点)与 `DirtySurface.def`(§5.2)。G1 codegen 规则:capability gate 替换指针表达式时保留**表达式形状**,否则 pull `.text` 漂移。 -**G1 的 codegen 规则**:capability gate 若替换的是指针表达式,必须保留其**表达式形状**(init-statement / short-circuit / pointer),不能只保留真假值——`MGL_BACKEND_SLOT_CAP` 与 pointer-valued `MGL_BACKEND_SLOT_PTR_CAP` / `_PTR_LOCAL` 分开,后者在 pull 展开回原 slot expression,否则 pull `.text` 会漂移。 +### 3.2 分组与 flag -### 3.2 分组与计数 - -| Class | 条 | 内容 | -|---|---|---| -| `kScreen` | 11 | `GetCaps`(R)、`ResourceCreate/Respecify/Destroy`、`MapPersistent`(R,O)/`UnmapPersistent`(O)、`FenceCreate/Status(R)/Wait(R)/Destroy`、`FenceWaitServer` | -| `kCtxQuery` | 8 | `QueryCreate/Begin/End/Available(R)/Result(R)/Destroy`、`QueryTimestamp`(R)、`QueryCounter` | -| `kCtxCso` | 13 | create/delete × {render state, vertex elements, sampler, sampler view, shader} + bind × {render state, vertex elements, shader} | -| `kCtxState` | 17 | `SetDynamicState`(B)、`SetFramebufferState`、`SetVertexBuffers`(V)、`SetIndexBuffer`、`SetIndirectBuffers`、`SetSamplerViews`(V)、`BindSamplerStates`(V)、`SetShaderImages`(V)、`SetShaderBuffers`(V,H)、`SetStreamOutputTargets`(V)、`SetGlobalConstants`(B)、`SetVertexAttribDefaults`(V)、`SetPixelPackState`、`SetPatchState`、`SetDrawProgram`、`SetDispatchProgram`、迁移期临时的 `SetResidualValueState`(B) | -| `kCtxObject` | 9 | `SetTextureParams`、`ResourceSubData`(B,V)、`BufferSubDataResident`(B,O)、`ResourceSubDataComplete`、`ResourceFlushRange`、`ResourceReadback`(R)、`ResourceCopyRegion`、`GenerateMipmap`、`GetTextureImage`(R) | -| `kCtxVerb` | 18 | `Blit`、`Clear`、`ReadPixels`(R)、`DrawVbo`(H,V)、`LaunchGrid`、`MemoryBarrier`、`Begin/End/Pause/ResumeStreamOutput`、`Flush`、`Present`、`SetSwapInterval`(O);P5b 追加 `BindShaderImage`、`PatchParameter`、`BindStreamOutput`、`SetStorageBlockBinding`、`CopyFramebufferToTexture` | - -Flags:`kNeedsAck`、`kHasBlob`(B,payload 拥有一个 `MGPBlobRef` 成员,不弱于此)、`kVarTail`(V)、`kHostSpan`(H)、`kReplySlot`(R,答进 `MGPReplySlot`)、`kOptional`(O,后端表里可为 null:Magma 不注册 `BufferSubDataResident` 与 `SetSwapInterval`)。 - -- 20 个 draw 入口塌成 `DrawVbo` 一条,`MGPDrawRange[]` 是 `MultiDraw*` 族的形状;`Clear` 一条判别式合并 `glClear` + `glClearBuffer*` + `glClearNamedFramebuffer*`。 -- `SetSamplerViews` / `BindSamplerStates` **没有 stage 维度**:MobileGL 的纹理单元空间是合并的(192 个单元一个数组),stage 只在目标 API 需要时由 server 从反射归档推导。 -- `SetTextureParams` 按资源寻址、与 sampler view 分开(D10):只作 attachment / image 单元 / copy 端点的纹理没有 sampler view,但 Espryt 对 attachment 也同步纹理参数。P4a 用白盒断言(`MG_IntegrationTest/Harness/PipeApplyPeek`)证明这个缺口此前确实是坏的。 -- `SetIndexBuffer` 独立于 VAO 配置版本(D5);`SetGlobalConstants` 只覆盖默认 uniform block(D6)。 - -**显式不移植**:`GetIntegeri_v`/`GetInteger64i_v`/`GetProgramiv`(后两项已从 `GLFunctionsTable` 删除;`GL_MAX_COMPUTE_WORK_GROUP_*` 进 `MGPCaps`)、`ShaderStorageBlockBinding` 的反射折叠、`set_pixel_unpack_state`(前端已在 `glTexImage` 时解析压缩格式、强制默认 unpack)、压缩格式概念、`pipe_transfer`。 +- Class:`kScreen`(caps、resource、persistent map、fence、`ApplierReset`)、`kCtxQuery`、`kCtxCso`(create/bind/delete × 五种 CSO)、`kCtxState`(`Set*` 状态、`SetContextValues`、`SetProgramBindings`、迁移期的 `SetResidualValueState`)、`kCtxObject`(subdata、readback、mip、`ObjectDeath` …)、`kCtxVerb`(blit、clear、`DrawVbo`、dispatch、XFB、`Present` …)。 +- Flags:`kNeedsAck`、`kHasBlob`、`kVarTail`、`kHostSpan`、`kReplySlot`、`kOptional`。第五列 `WaitClass`(P5e)决定 run-ahead 下 client 等不等(§11.6)。 +- 20 个 draw 入口塌成 `DrawVbo` 一条;`Clear` 一条判别式;sampler 集合**没有 stage 维度**(192 单元合并空间);`SetTextureParams` 按资源寻址、与 sampler view 分开(D10);`SetIndexBuffer` 独立于 VAO 配置(D5)。 +- 显式不移植:`GetIntegeri_v` / `GetInteger64i_v` / `GetProgramiv`、`set_pixel_unpack_state`(前端已解析)、压缩格式概念、`pipe_transfer`。 ### 3.3 能力位(`MGPCapBit`) -`kCapViewportArray`、`kCapFloat64VertexAttrib`、`kCapResidentSubData`、`kCapCpuXfbPrimitiveAccounting`、`kCapTimerQuery`、`kCapOcclusionQuery`、`kCapXfbPrimitivesQuery`、`kCapNeedsHostIndexBytes`(server 做 restart 重写 / multi-draw 展平,split 下开启索引宿主镜像,§10.3)、`kCapNeedsHostUboBytes`(server 把具名 UBO 打进自己的 ring)、`kCapBackendOwnsXfbCapture`(P5b)。`CallMask` 取代"槽位是否为 null"这个隐式能力探测。 - -不存在 `kCapPrimitiveRestart` / `kCapMultiDraw*` 一类"归属开关"(D-B7):**multi-draw 分档与 restart 重写永远由 server 拥有;client 在 caps 说需要时提供索引字节。** `MGPCaps` = `DynamicBackendParameters` 整块 + `CallMask` + 两个 blob(format 能力表、renderer 字符串),握手后一次快照,取代 40 个 `pActiveBackendObject->` 站点与 89 个 caps 读点。 +`CallMask` 取代"槽位是否为 null"这一隐式能力探测;`MGPCaps` = `DynamicBackendParameters` + `CallMask` + 格式能力表与 renderer 字符串两个 blob,握手后快照、`MakeCurrent` / `InitCapabilities` 后按 generation 重发布。位包括 viewport array、fp64 顶点、`kCapResidentSubData`、XFB / query 家族、`kCapNeedsHostIndexBytes`、`kCapNeedsHostUboBytes`、`kCapBackendOwnsXfbCapture`、`kCapRunAheadApply`(`MGPipeRunAheadCapBitsFor`:Espryt 自 P5e、Magma 自 P5f 之后的 Magma run-ahead 起发布,后者契约 `MG_Remote/CONTRACT-MAGMA-RUNAHEAD.md`)。**不存在"归属开关"(D-B7)**:multi-draw 分档与 restart 重写永远由 server 拥有,client 只在 caps 要求时提供索引字节。 ## 4. 记录与 payload 约定(`MG_Pipe/MGPipeTypes.h`) -- 每个 payload 是平坦 POD、显式 padding、`static_assert` 平凡可复制与精确尺寸;**永不含指针**。 -- `MGPBlobRef{Offset, Size, Seg}`(24 B)指向 blob 区:monolith 下 `Seg == kMGHostSpanSegNone`;split 下 Seg 命名传输段。 -- `MGHostSpan`(32 B)是唯一形状随传输而变的东西:monolith 下 `Ptr` 指向 shadow 或应用内存;split 下 `Ptr == nullptr`、字节在 `SEG_STAGE`,或 `Seg == kMGHostSpanSegFromServerIndexMirror`(P8)。它只进变长尾(`DrawVbo` 的用户索引、`SetShaderBuffers` 的具名 UBO 字节),永不内联进定长 payload——VBO 路径不为它付字节。 -- 变长记录(`kVarTail`)= 定长前缀 + 自描述长度的内联尾巴;`kHasBlob` 记录额外校验 `BlobRef` 落在其声明的段内。`SEG_CMD` 是对端并发写入的区域,运行期违反一律 `Fatal{ProtocolCorruption}`。 -- wire 记录头 `MGPWireRecHeader{Op:u16, Flags:u16, Size:u32}`(8 B),Size 含头、8 字节倍数;**没有逐记录序号字段**——seq 就是记录序数(producer `m_emitSeq++` / consumer `m_applySeq++`)。`Flags` 是 ring 封帧,永不是 call flags(R-17)。 -- 单条记录上界 = ring 容量的一半(`RingProducer::MaxRecordBytes()`),超过的记录被具名拒绝(`Fatal{RingOverrun}`)——**记录本身永不分块**(R-10;实测 `maxrec` 见 `MEASUREMENTS.md` §6.3)。**内容侧已分块**:会超出 `SEG_STAGE` 的 blob 由发射侧按 stage chunk 预算 `MGPipeStageChunkBytes()`(`clamp(segment/4, 4096, segment)`,默认 8 MiB)切成多条记录——buffer 范围走查(`PipeFill.cpp`)与纹理一级的整宽 slab(`TextureEmit.h`,服务端 `StagedTextureStore::AdoptRun` 拼回整级);单片仍超容量或该 record 类型未接入分片时仍是 `Fatal{RingOverrun, "SEG_STAGE"}` 兜底。 -- `MGPSubData` 的 buffer 半边:`Target == Buffer` 时目的字节范围搭在 `UnionBox.X/W` 上,`MGPipeSetSubDataBufferRange()` 是唯一拼写;单条记录上限 offset 2³¹−1 / size 2³²−1。 +- 每个 payload 是平坦 POD、显式 padding、`static_assert` 精确尺寸;**永不含指针**。 +- `MGPBlobRef{Offset, Size, Seg}` 指向 blob 区;`MGHostSpan` 是唯一随传输而变的形状(monolith 下是指针,split 下字节在 `SEG_STAGE`),只进变长尾。 +- wire 记录头 `MGPWireRecHeader{Op, Flags, Size}`(8 B);**没有逐记录序号**,seq 就是记录序数。 +- 单条记录上界 = ring 一半,**记录本身永不分块**(R-10);**内容侧分块**:会超出 `SEG_STAGE` 的 blob 按 `MGPipeStageChunkBytes()`(默认 segment/4 = 8 MiB)切成多条记录(buffer 范围走查、纹理整宽 slab,server 拼回整级)。 ### 4.1 关键 payload -| payload | 尺寸 | 要点 | -|---|---|---| -| `MGPResourceDesc` | 88 | buffer / 全部纹理 target / renderbuffer 一个判别式 create/respecify 形状;`BindMask` 的 `ELEMENT_ARRAY` 位是索引镜像的开关;`ImageBindableHint` 预防性分配 image-bindable 存储;`ViewOf` 是纹理视图的存储属主;`BufferForTexBuffer/BufOffset/BufSize` 实时解析;`HasDefinedContent` 陈述的是上一次 respecify 当时的事实 | -| `MGPRenderStateDesc` / `MGPBindRenderState` / `MGPDynamicState` | 48 / 12 / 32 | §5.3 | -| `MGPVertexElements` | 40 | blob 带解析后的 `MGPVertexAttribWire[]` **和** `MGPVertexBindingPointWire[]`(记录自洽:一条不描述自己 blob 的记录让边界门无法收口;代价按配置变化付一次;裁掉第二个视图是 P13 的重调项);`IsLong` 与 `Type == Float64` 分开携带 | -| `MGPSamplerDesc` | 32 | `SamplerParameters` 逐字节过线**含 `borderColorForm`** | -| `MGPSamplerView` / `MGPTextureParams` | 36 / 40 | view 只带视图限制;纹理参数挂在纹理对象上。**D-E1**:`MGPTextureParams::BuiltinSampler` 是该纹理自带 `SamplerObject` 对应的 sampler CSO 句柄(从内容寻址 cache 取,**不允许为空**——空句柄是协议损坏)+ `SamplerResync` 位 | -| `MGPProgramDesc` | 192 | 逐 stage SPIR-V blob ×6 + 反射归档 blob + `StageMask`/`GlobalUboSize`/`ReservedNumSamplesOffset`,§7 | -| `MGPFramebufferState` | 304 | 8 color + depth + stencil + **client 解析后的 `ReadSurface`**;`MGPSurface::InternalFormat` 与 `TextureTarget` 内联;`ContentHash` 既是 render-pass memo 键也是发射抑制器;`Target` 字节按绑定目标发(`Draw` / `Read` / `Both` / **`Named = 3`**),applier 按 framebuffer 句柄存成每对象一张表外加两个"当前绑定"句柄——否则 `BlitNamedFramebuffer` / `ClearNamedFramebuffer*` 会打进一个从未收到附件的 FBO | -| `MGPSubData` / `MGPSubRegion` | 72 / 40 | §6 | -| `MGPDrawInfo` / `MGPDrawRange` / `MGPDrawIndirect` | 56 / 12 / 40 | `Flags` 门控 `MinIndex/MaxIndex`(只在 client-memory 数组路径算)与 `XfbCpuCapturedVertices`;`NumDraws` 个 `MGPDrawRange` 在变长尾;用户索引的 `MGHostSpan` 只在 `kDrawHasUserIndices` 时进变长尾;indirect 的 `DrawCount` 由 client 解析 | -| `MGPShaderBuffers` / `MGPBufferRange` | 32 / 24 | `kCapNeedsHostUboBytes` 下 Uniform 类带第二个变长尾 `MGHostSpan[HostSpanCount]` | -| `MGPPixelPackState` / `MGPPatchState` / `MGPClear` / `MGPGlobalConstants` / `MGPSubDataComplete` | 28 / 40 / 48 / 40 / 24 | 只有 PACK 方向(D5);patch 同时是 shader variant 输入;clear 五种判别式;`(ShaderCso, Version)` 键控;纹理拉取的正向终止符,可携带零个 region | -| `ResidualValueBlock` | 8 | 迁移期 Track V 载体,§9.4 | +| payload | 要点 | +|---|---| +| `MGPResourceDesc` | buffer / 纹理 / renderbuffer 一个判别式 create/respecify 形状;`ImageBindableHint`、`ViewOf`(视图的存储属主)、`HasDefinedContent` | +| `MGPRenderStateDesc` / `MGPBindRenderState` / `MGPDynamicState` | §5.3 | +| `MGPVertexElements` | blob 同时带解析后的属性与绑定点视图(记录自洽) | +| `MGPSamplerView` / `MGPTextureParams` | view 只带视图限制;纹理参数挂纹理对象,`BuiltinSampler` 句柄不许为空(D-E1) | +| `MGPProgramDesc` | 逐 stage SPIR-V + 反射归档(§7) | +| `MGPFramebufferState` | 8 color + depth + stencil + client 解析后的 read surface;`ContentHash` 同时是 render-pass memo 键与发射抑制器;`Target` 含 `Named = 3`,applier 按 framebuffer 句柄存 | +| `MGPSubData` / `MGPSubRegion` | §6 | +| `MGPDrawInfo` / `MGPDrawRange` | `DrawVbo`;multi-draw 范围与用户索引在变长尾,indirect 的 `DrawCount` 由 client 解析 | -每条 `kVarTail` 的 `set_*` 都带 `ContentHash`——hash 未变就不发(§5.4)。 +每条 `kVarTail` 的 `set_*` 带 `ContentHash`,hash 未变就不发(§5.4)。 ## 5. 前端 state tracker(`MG_Impl/Pipe/Tracker`) ### 5.1 推送发生在 verb 之前的 validate 时刻,不在 GL setter 里 -Blaze3D 每个 batch 用 `glEnable/glDisable(GL_BLEND)` 包住,per-setter 推送会把每次冗余开关变成一次接口调用加一次 server 侧 CSO 查表,严格慢于今天。正确形态是 gallium `st_validate_state`。 +per-setter 推送会把 Blaze3D 每个 batch 的冗余 `glEnable/glDisable` 变成接口调用,严格慢于今天;正确形态是 gallium `st_validate_state`。落地为唯一的 `MGPipeValidateForVerb(MGPipeVerb)`(九个 validate 类,`FillPoints.def`),恰在每次经表调用之前、每个提前返回之后。只有今天就在 GL 调用时刻分发的资源 op(`BufferBackendOps` 的七个 hook)在调用时刻推送。 -**九个** validate 类(`FillPoints.def`):`kDraw`、`kDispatch`、`kClear`、`kBlitOrCopy`、`kTextureOp`、`kReadback`、`kXfbSpan`、`kProgramOp`、`kQuery`。落地形态是唯一的 `MGPipeValidateForVerb(MGPipeVerb)`(`MG_Impl/Pipe/PipeFill.h`),恰好是每次经函数指针表调用之前的一条语句、位于每个提前返回之后,verb 到类的映射由生成表回答。九个而不是四个,因为约 48 个非 draw 表项自己就读 `pGLContext`。 +### 5.2 dirty 位:值类零新增记账,对象类 5 个聚合世代 -**只有今天就在 GL 调用时刻分发的资源 op 在 GL 调用时刻推送**——即 `BufferBackendOps` 的七个 hook。纹理 subdata 不在此列(§6)。 +- 值类组(render state、pixel pack、patch、attrib defaults、shader bindings …)直接复用既有版本计数器作快门。 +- 对象类组(vertex buffers、framebuffer、sampler views / samplers / images、const / shader buffers、SO targets)靠 5 个**聚合世代**把快门降成一次 `Uint64` 比较——没有聚合就回答不了"有没有哪张已绑定纹理动了"。 +- 完整性由 `scripts/gen_pipe_dirty_surface.py` 保证(每个 mutator → 必须 bump 的世代,`--check` + `--self-test` 进 CI)。**快门看不见自己的主体**是踩过三次的坑:新增字段必须带"记录字段 → setter → 快门"一行,优先混入已有世代(新计数器会撑大 pull 对象,G1 不允许)。 -### 5.2 dirty 位:值类零新增记账,对象类新增 5 个聚合世代 - -| dirty 位 | 类 | 快门来源 | -|---|---|---| -| `NEW_RENDER_STATE` / `NEW_PIPELINE_STATE` | 值 | `m_version` / `m_pipelineStateVersion` | -| `NEW_PIXEL_PACK`、`NEW_PATCH_STATE`(`BitwiseEqual`,NaN 合法)、`NEW_VERTEX_ATTRIB_DEFAULTS`、`NEW_VERTEX_ELEMENTS` | 值 | 既有计数器 | -| `NEW_SHADER`、`NEW_SHADER_BINDINGS`、`NEW_GLOBAL_CONSTANTS` | 值 | link / image-unit / backend-state / block-binding / uniform-write-set / UBO-content 版本 | -| `NEW_VERTEX_BUFFERS` | 对象 | `VertexArrayState::m_anyVaoAttributeGeneration` → 命中后走 32 属性前缀 | -| `NEW_INDEX_BUFFER` | 对象 | 索引 slot 版本 + 绑定对象 `{slot,gen}` | -| `NEW_FRAMEBUFFER` | 对象 | `FramebufferState::m_anyAttachmentGeneration` + 对象/slot 版本 → 重算 `ContentHash` | -| `NEW_SAMPLER_VIEWS`、`NEW_SAMPLERS`、`NEW_SHADER_IMAGES` | 对象 | `TextureState::m_anyTextureContentGeneration` + `m_anyTextureParamsGeneration` + bind / sampling-resolution generation → `GetMaxTouchedUnit()` 前缀、重算集合 hash | -| `NEW_CONST_BUFFERS` / `NEW_SHADER_BUFFERS` / `NEW_SO_TARGETS` | 对象 | `BufferState::m_anyBufferChangeGeneration` → `GetTouchedBindPointCount()` 前缀 | - -五个聚合世代落在既有 bump 点上,把对象类组的快门降成一次 `Uint64` 比较;对象类不能靠轮询逐对象版本(没有聚合能回答"有没有哪张已绑定纹理动了")。完整性由 `scripts/gen_pipe_dirty_surface.py` 保证:枚举每个 mutator → 必须 bump 的聚合世代,未映射或映射行点名一个已不存在的 mutator 即失败(`--check` + `--self-test` 都是 CI 门)。**快门看不见自己的主体**是 P4a 踩过三次的坑(`glBindSampler`、SSO 下 `GetCurrentProgram()`、`glBindImageTexture` 只换 level)——新增字段必须带"记录字段 → setter → 快门"一行,且优先混入已有世代(新增计数器会撑大 pull 对象,G1 不允许)。 - -三个回绕 `Uint16` 在 tracker 边界加宽(`m_lastPushed[]` 是 tracker 自己的字段);回绕在 tracker 本地无害(多一次重推,永不漏推)。 - -### 5.3 渲染状态:整块 blob 过线,身份只取 pipeline 子集,动态状态单独走(D-B1) +### 5.3 渲染状态:整块 blob 过线,身份只取 pipeline 子集(D-B1) ``` -create_render_state(cso, MGPBlobRef pipelineSubsetChunks) // 只带 pipeline 子集 -bind_render_state(cso, Uint16 version, Uint16 pipelineVersion) // 稳态 12 B -set_dynamic_state(MGPBlobRef dynamicChunks, Uint16 version) // 只带动态子集的变化 chunk +create_render_state(cso, pipelineSubsetChunks) // 只带 pipeline 子集 +bind_render_state(cso, version, pipelineVersion) // 稳态 12 B +set_dynamic_state(dynamicChunks, version) // 只带变化的动态 chunk ``` -- 整块:`RenderStateParameters` 是平凡可复制 POD,Espryt 做 head/blend/tail 三段 memcmp,**字段顺序承重**;拆成三个 CSO 要手工维护 ~150 字段划分表且无绊线。 -- 子集身份:整块内容寻址会让 `glViewport`/`glScissor`/`glClearColor` 每次铸造新 CSO、冲掉 server 的 pipeline memo(`RenderState.h` 记录的那次回归)。 -- 划分只写在一处:`MG_Pipe/MGPipeRenderStateSpans.{h,cpp}` 的 chunk 表(16 个 `constexpr` 边界,两半交替:**7 个 pipeline chunk 共 396 B + 8 个 dynamic chunk 共 772 B = 1168**,总数与划分完整性是 `static_assert`)+ `MGPipeComputePipelineSubsetHash()`,client 与两个后端共用。G7 的 `RenderStateSpansTest` 遍历每个 setter,断言 `pipelineSubsetHash 变 ⟺ m_pipelineStateVersion 变`。 -- server 侧:每 context 一份 working `RenderStateParameters`,`bind` 与 `set_dynamic_state` 各把自己的 chunk 散射进去;**Espryt 的 `SyncRenderState` 一行不动**;Magma 的 pipeline memo 键是 `cso.slot`。 -- client 取值顺序:`m_pipelineStateVersion` 未变 → 复用上一个 CSO handle,零哈希;变了 → 对 396 B 子集算 xxHash → map 探测 → 命中发 12 B bind,未命中发 create 再 bind;`m_version` 变而子集未变 → 只发 `set_dynamic_state`。 -- P2 补了三个此前无存储的 capability:`FramebufferSrgb`、`DepthClamp`、`TextureCubeMapSeamless`,落在 `ColorMasks` 与 `ClearColor` 之间的 3 字节空洞,`sizeof` 仍 1168、既有成员一个没挪位。 +- `RenderStateParameters`(1168 B)整块过线:Espryt 做三段 memcmp,字段顺序承重;拆成多个 CSO 要手工维护 ~150 字段划分且无绊线。 +- 身份只取 pipeline 子集,否则 `glViewport` / `glScissor` 每次都铸新 CSO、冲掉 server 的 pipeline memo。划分只写在一处:`MGPipeRenderStateSpans` 的 chunk 表(7 pipeline chunk 396 B + 8 dynamic chunk 772 B,`static_assert` 完整性),G7 断言"子集 hash 变 ⟺ `m_pipelineStateVersion` 变"。 +- Espryt 的 `SyncRenderState` 一行不动;Magma 的 pipeline memo 键是 `cso.slot`。 ### 5.4 验证不变式、合并与抑制器 -规范(D-B3):**一条 verb 的全部 `set_*`/`bind_*` 必须在该 verb 之前完成;server 在 verb 处从它此刻持有的全部已推送状态惰性特化 shader 与 pipeline。除"资源 create 先于对它的 bind"外,`set_*` 之间没有顺序要求。** 退役 Espryt 的 fragColor 重推导 workaround、`g_broadcastMemo*` 与 `ImageUnitFormatsStillMatch` 的机制是惰性特化,不是调用顺序。 - -`create_shader_state` 从编译池的终止 continuation 发出(不是从 draw),SPIR-V 在首个用到它的 draw 之前到达 server。 - -合并规则:整块结构优于逐字段;高水位标记留在 tracker 走查里、直接是 `count` 实参;只发 program 解析过的集合;**集合 hash 抑制器**——每条 `kVarTail` `set_*` 在 client 算已解析集合的 xxHash,未变不发(从后端搬到 client 的 ~175 行去抖;MC 26.2 每次纹理单元切换都重绑同一个 sampler)。索引绑定范围在 validate 时刻实时解析。 +规范(D-B3):**一条 verb 的全部 `set_*` / `bind_*` 在该 verb 之前完成;server 在 verb 处从已推送状态惰性特化 shader 与 pipeline。**除"资源 create 先于对它的 bind"外,`set_*` 之间无顺序要求。合并:整块优于逐字段;高水位直接是 `count` 实参;只发 program 解析过的集合;**集合 hash 抑制器**在 client 侧去抖(原先在后端的 ~175 行)。 ### 5.5 sampler view 在 client 侧解析 -GL 是每 unit 每 target 各一个绑定;shader 看见哪一个取决于 sampler uniform 类型、mipmap 完备性与 `IsUndefinedDefaultTexture`。gallium 的"每槽一个 view"就是解析后的形态,解析留在 client 并带自己的 memo。两处后端特定后处理留在 server:Espryt 的 raw-depth-fetch sampler 替换、Magma 的 feedback-loop 检测。 +shader 看见哪个纹理取决于 sampler uniform 类型、mipmap 完备性与未定义默认纹理;解析留在 client(带 memo),gallium "每槽一个 view" 就是解析后的形态。两处后端特定后处理留在 server:Espryt 的 raw-depth-fetch sampler 替换、Magma 的 feedback-loop 检测。 ### 5.6 生命周期、共享组、composite program -- `resource_create` 在前端对象构造时发,存储由 `resource_respecify` 惰性定义;`resource_destroy` 在析构时发。顺序约束由 payload 表达:view 先于存储属主销毁(`ViewOf` + server keep-alive)、FBO attachment 钉住纹理、buffer texture 钉住 buffer。死亡在 wire delete 与 slot free 之间转发给每个 emitter(六个死亡 helper 只释放 slot 曾导致 UAF)。 -- 共享组:v1 一个 screen、一个 context、一条 flow;`eglMakeCurrent` 是 flow 所有权转移。 -- program pipeline 合成体(`MG_Impl/Pipe/CompositeResolver.h`):`GLContext::GetProgramForDraw()` 完全在前端合成,tracker 推**一个** handle,slot 从 `ShaderCso` 保留段分配,pipeline cache 淘汰时释放 slot、`gen++`、发 `delete_shader_state`。释放有两条路且必须恰好一次:合成体被换掉时由 resolver 释放,程序对象死亡时由 `~ProgramObject` 释放(D-H7)。**记忆按 `(ContextId, 管线 GL 名)` 键控**——resolver 是进程级单例而 GL 名是每上下文的。 +- `resource_create` 在前端对象构造时发,存储由 `resource_respecify` 惰性定义,`resource_destroy` 在析构时发;死亡转发给每个 emitter(只释放 slot 曾导致 UAF)。view 先于存储属主销毁、FBO attachment 钉住纹理、buffer texture 钉住 buffer。 +- program pipeline 合成体(`CompositeResolver`)完全在前端合成、推一个句柄;释放恰好一次(resolver 换掉或 `~ProgramObject`,D-H7);记忆按 `(ContextId, 管线 GL 名)` 键控。 ### 5.7 emulation 的归属 -规则:**驱动表达不了的变换在 tracker 里 lowering,硬件/驱动强加的变换在 driver 里 lowering。** 只有三个"读前端字节的纯 CPU 变换"下放到 client。 +规则:**驱动表达不了的变换在 tracker 里 lowering,硬件 / 驱动强加的变换在 driver 里 lowering。** -| emulation | 归属 | 过线的是什么 | -|---|---|---| -| client 顶点数组 | client | 字节(`MGHostSpan`),永不是指针 | -| 最大索引扫描(`TryComputeMaxIndexFromHostBytes`) | client | `MGPDrawInfo::MinIndex/MaxIndex`(flag 门控) | -| client 索引数组 | client | 变长尾里的 `MGHostSpan` | -| `*IndirectCount` 计数解析 | client | 解析后的 `MGPDrawRange[]` | -| primitive-restart 重写(整 EBO) | **server** | 零线上流量:从索引宿主镜像读(§10.3) | -| multi-draw 五档分档 + 展平 | **server** | 同上 | -| viewport-array N 遍回放 | server | 无新增(16 组 viewport/scissor/depth-range 已在渲染状态里) | -| fp64 顶点窄化 | server | 原始字节;`IsLong` 与 `Type` 分开过线 | -| image-bindable 存储加宽/拆分 | server | 正向 `ImageBindableHint`;反向纹理拉取 + 终止符 | -| 生成 mipmap 的前端存储 | 拆开:client 分配 level 存储,server 生成 | `MGPMipPlan`;`OnMipLevelsGenerated` 只带形状 | -| CopyImage shadow 镜像 | client | 只回"拷贝成功" | -| XFB CPU 图元计数 / scatter 的 read-modify-write | client | `XfbCpuCapturedVertices`、`MGPXfbAccounting`;§8.5 | -| 压缩纹理 / pixel unpack 规整 | client | 无 | +| 归 client(读前端字节的纯 CPU 变换) | 归 server | +|---|---| +| client 顶点 / 索引数组(字节过线,永不是指针)、最大索引扫描、`*IndirectCount` 计数解析、CopyImage shadow 镜像、XFB CPU 图元计数、压缩纹理 / unpack 规整 | primitive-restart 重写、multi-draw 分档与展平(从索引宿主镜像读,§10.3)、viewport-array 回放、fp64 顶点窄化、image-bindable 存储加宽、生成 mipmap(client 分配 level 存储) | -**陈旧索引纪律是逐站点表**,client 侧扫描/解析之前的 reconcile 必须逐字复现 monolith 的集合:最大索引扫描(EBO 源)= `SyncPersistentMappedRange()` **+** `SyncGpuWrites()`;`*IndirectCount` 计数解析 = **只** `SyncPersistentMappedRange()`(monolith 今天就只做这一个);client 顶点数组范围计算与 client 指针源扫描 = 无;server 侧重写 / 展平从镜像读,GPU 写者可见性由 `OnGpuWritten` 收窄集在 server 本地判定。门:`ClientArrayAfterComputeWriteScenario`(去掉等待必须看到几何缺失);`create-indirect` fixture 上 `roundtrips-per-frame` 必须读零(P8)。 +**陈旧索引纪律是逐站点表**:client 侧扫描前的 reconcile 必须逐字复现 monolith 的集合(最大索引扫描 = `SyncPersistentMappedRange()` + `SyncGpuWrites()`;`*IndirectCount` 只前者)。 ## 6. 纹理 subdata 与 dirty 归属 -- `glTexSubImage*` 根本不调后端表:全部纹理上传由 Espryt 在 sync 时刻按**累积**区域做(96-rect 级联合并、union-box 回退、unpack ring 可用时塌成一个 box)——Mali 按**作业数**给上传计价,~100 个精灵 rect 对一个 union box 实测 +6 ms/frame。逐 `glTexSubImage` 发一条记录会精确复现那个形状。 -- 因此 client 在自己的 `MipmapStorage` rect 模型里累积,在**下一个 validate / flush 点**把合并后的形状作为**一条** `ResourceSubData` 发出,**同时携带 union box 与 region 列表,由 server 选上传形状**(决策留在付 GPU 代价的那一侧;`MEASUREMENTS.md` §1.3:vanilla 世界同样 185 次发射,Espryt 整 box 635 KB/帧、Magma rect 40 KB)。 -- `MGPSubRegion` 显式携带 `SrcRowStride/SrcSliceStride`,`MGPSubData::SourceIsVerbatimLevelShadow` 显式回答原来由指针比较回答的问题;Espryt 的上传路径改为从描述符取步长。 -- **dirty 归属反转**:client 保留 rect 模型、维护发射游标、发射后清自己的标志,server 从不碰 client 的标志(`MG_Impl` 里没有任何 `IsStorageDirty` 调用点)。**P4a 落地的是排水列表**——client 在 validate 点把每个脏的 `(存储属主, 上传目标, level)` 发成一条 `resource_subdata`;按存储属主键控的发射游标与 view 索引重映射是 P3b/P4b 的。两条规矩:**清 dirty 要等 applier 的 acceptance**(被拒的记录必须让 level 留在脏表里);**服务端自己的重新变脏是服务端的事**(twin 直接重新武装 applier 记录里的待上传项)。 -- 后端在 shadow 里写字节的两处——CPU 回退生成 mip 与 `glCopyImageSubData` 目的地镜像——分别由 `OnTextureWriteback` 与"CopyImage 镜像搬到 client"处理。Unpack PBO 完全在 client 解析;`glCopyTexSubImage*` 与 `glClearTexImage` 整体留在 client。 -- `TextureUploadShapeScenario`(形状金标)已建好但只记录不设门,等 Mali 侧的帧时增量才升级成门。 +- 纹理上传由 Espryt 在 sync 时刻按**累积**区域做(Mali 按作业数计价,~100 个 rect 对一个 union box 实测 +6 ms/帧),逐 `glTexSubImage` 发记录会复现这个悬崖。 +- 因此 client 在自己的 rect 模型里累积,在下一个 validate / flush 点发**一条** `ResourceSubData`,同时携带 union box 与 region 列表,**由 server 选上传形状**(决策留在付 GPU 代价的一侧)。步长显式携带,不再靠指针比较。 +- **dirty 归属反转**:client 维护发射游标、发射后清自己的标志;清 dirty 要等 applier 的 acceptance(被拒的 level 留在脏表里);server 自己的重新变脏是 server 的事。 +- `TextureUploadShapeScenario` 把上传形状录成金标(P3b/P4b D2 起是门;Mali 帧时增量无设备,按 Adreno 记录)。 ## 7. Shader state = SPIR-V + 反射归档 -- `CreateShaderState` 的 payload 是逐 stage SPIR-V + 反射归档(`LinkArtifacts` + `SpirvArtifacts` 全结构体),**不是源码**。glslang 全在 client,SPIRV-Cross 全在 server,文件级切割;没有 `MOBILEGL_IPC_PROGRAM` 开关、没有 server 侧 compile pool。 -- 归档机制:`Visit()` + `sizeof` 绊线。序列化器在树里(`MG_Pipe/ProgramArtifactsCodec.{h,cpp}`),**monolith 只在 verify 构建里调它**(D-H3:制品不在 monolith 过线,编解码不在热路径上)。未完:`ProgramArtifacts.h` 的 libc++/NDK 尺寸钉仍是惰性的(`MGL_ARTIFACT_SIZES_LIBCXX_PINNED` 未定义),记在 P3b/P4b。 -- P0.5 把反射类型抽到 `MG_State/GLState/ProgramState/ProgramArtifacts.h`、值类型抽到 `MG_Pipe/MGPipeValueTypes.h`(不 include `MG_State/GLState`),CI `-H` 闭包断言把关;没有这一步 P7 的 `nm -D | grep glslang` 判据不可达。 -- server 侧惰性特化(D-B2):后端 program 还依赖 8 个额外输入(draw FBO 的 clamp mask、fragColor 广播数、storage-block 绑定签名、atomic counter 集、活的 image 格式、patch 参数;Magma 另加 Y-flip 高度与 XFB 布局),`create_shader_state` 发布**制品**,server 在 verb 时刻从已推送状态特化——正是两个后端今天的做法。 -- 后端 link/compile 失败不需要同步返回:`GL_LINK_STATUS` 永不撤回,同步查询由 client 从 `ProgramObject` 回答,失败以 `OnLog` ≥ERROR 无损呈现。 -- Magma 的两个内部 shader(blit、depth-mipmap)烘焙成签进树的 SPIR-V,`MG_Test` 重跑树内 glslang 逐字节比对守新鲜度(`MOBILEGL_BAKED_INTERNAL_SHADERS`,P7)。 +- `CreateShaderState` 过线的是逐 stage SPIR-V + 反射归档,**不是源码**:glslang 全在 client、SPIRV-Cross 全在 server,文件级切割;server 侧无编译池。归档序列化器 `ProgramArtifactsCodec`,monolith 只在 verify 构建里调它(D-H3)。 +- server 在 verb 时刻从已推送状态**惰性特化**(D-B2:draw FBO clamp mask、fragColor 广播数、storage-block 绑定签名、image 格式、patch 参数等)——正是两个后端原来的做法。 +- link / compile 失败不需要同步返回:`GL_LINK_STATUS` 由 client 从 `ProgramObject` 回答,后端失败以 `OnLog` ≥ERROR 无损呈现。 +- 内部 shader(颜色 blit、深度 mip、multisample resolve)烘焙成签进树的 SPIR-V(`Wire*Spirv.h`),新鲜度门 `MOBILEGL_BAKED_INTERNAL_SHADERS`(`MG_Test/SelfTest/BakedInternalShadersTest.cpp`)重跑树内 glslang 逐字节比对(P7)。 ## 8. 反向通道 ### 8.1 `MGPipeCallbacks`(`MG_Pipe/MGPipeCallbacks.h`) -十个具名回调 + 一个正向终止符(`ResourceSubDataComplete`),取代今天 95 个调用点 / 17 个方法直接 poke 前端对象。gallium 没有这些词汇(Mesa 里两者共享地址空间),具名化是有意偏离(D8)。monolith 下直调,split 下是 `SEG_EVENT` 上的记录。 +九个具名回调 + 一个正向终止符(`ResourceSubDataComplete`),取代后端直接 poke 前端对象的 95 个调用点(具名化是有意偏离 D8)。monolith 下直调,split 下是 `SEG_EVENT` 上的记录。 -| 回调 | 取代 | +| 回调 | 作用 | |---|---| -| `OnGlError(code)` | 6 处 `RecordError`;**必须对命令流有序**(`glGetError` 本身永远本地) | -| `OnGpuWritten(res, ranges[])` | 6 处 `MarkGpuWritten`:client 在每个 draw/dispatch 发射点保守自建 pending 集,这是**收窄**通道 | -| `OnBufferWriteback(res, offset, bytes)` | PBO 回读、XFB 捕获;按操作级批处理;必须与 epoch bump 有序 | -| `OnTextureWriteback(res, box, bytes)` | CPU 回退生成 mip 的纹素(唯一生产者) | -| `OnTexturePullRequest(res, target, firstLevel, levelCount, pullSerial)` | §8.4 | -| `OnMipLevelsGenerated(res, base, count)` | 只带形状,与 monolith 的 `EnsureGenerateMipmapStorageAllocated` 行为一致 | -| `OnSurfaceChanged(info)` | `SwapchainObject` 写 `pDefaultFramebufferInfo` 的分层倒置;client 自己合成 default-FB 对象 | -| `OnCapsInvalidated()` | 2 处 `InvalidateCompileEnv` | -| `OnLog(level, text)` | ≤WARN 有损,≥ERROR 无损 + 速率限制 | +| `OnGlError` | 延迟的 GL 错误;**必须对命令流有序** | +| `OnGpuWritten` | GPU 写过的 buffer 范围(收窄 client 保守自建的 pending 集) | +| `OnBufferWriteback` | PBO 回读、XFB 捕获结果;必须与 epoch bump 有序 | +| `OnTextureWriteback` | CPU 回退生成 mip 的纹素 | +| `OnTexturePullRequest` | §8.4 | +| `OnMipLevelsGenerated` | 只带形状 | +| `OnSurfaceChanged` | 默认帧缓冲的格式与尺寸(server 拥有显示时 client 靠它得知窗口尺寸) | +| `OnCapsInvalidated` | 取代 `InvalidateCompileEnv` | +| `OnLog` | ≤WARN 有损,≥ERROR 无损 + 速率限制 | -95 个写回点的其余归属:`MarkStorageDirty` 大多是 server 本地记账;后端凭空造的前端对象(Magma 占位纹理、swapchain default-FB 占位)→ server 原生;`SetBackendStateMemo` 直接删除;`SetBackendHashMemo/AuxMemo` → server 侧 per-slot 字段。**D-K(P3a)**:`PipeResource::m_backend` / `SetBackendResource` / `ReleaseBackend` / `BackendBufferResource` 在 push 下只是不再被写,真删会移动 pull 构建里 `sizeof(BufferObject)`(G1 破坏),随 pull 路径在 P13 退役。 +后端凭空造的前端对象(Magma 占位纹理、swapchain 默认 FB 占位)改为 server 原生;pull 构建里的 `m_backend` 类成员真删会动 `sizeof`(G1),随 P13 退役(D-K)。 ### 8.2 有序性是正确性要求 -每一次 `WritebackFromBackend` 后面都紧跟 `BumpBufferMutationEpoch()`,否则 server 的 draw-clean memo 会在 epoch 背后变陈旧——split 里这是反向通道上的排序规则:写回的 epoch bump 必须在任何后续读该 handle 的命令之前被 server 应用。**反向通道需要与正向通道相同的有序保证。** +每次 writeback 之后的 epoch bump 必须在任何后续读该 handle 的命令之前被 server 应用。**反向通道需要与正向通道相同的有序保证。** ### 8.3 错误、ack 与日志 -- 纹理分配的 OOM 在 monolith 里就已推迟到 sync 时刻,拆分不改变可观察行为,不同步 ack。**唯一允许同步 ack 的入口是 `glBufferStorage`**;`glRenderbufferStorage*` 不 ack(41 个 trace fixture 里 OOM 探测惯用法出现 0 次,语料里的成功性检查是 `glCheckFramebufferStatus`,client 本地作答)。 -- `ResourceRespecify` 携带 `kNeedsAck` 并带逐记录谓词 `MGPipeResourceRespecifyNeedsAck(desc)`(`Immutable != 0` **且是 buffer 目标**,D-A2——`glTexStorage*` 的 `Immutable` 同样是 1,不收窄每次不可变纹理分配都会变成同步往返);另有**逐 level 作用域**(`MGPRespecifiedLevel` 尾;不带 level 时 `glTexImage2D(level 1)` 会把已接受的第 0 级待上传一起丢掉)与**元数据更新**(描述符逐项相同时只换 `BindMask`/`ImageBindableHint`,不 ack、不清待上传)。 -- 其余错误一律晚到,走有序的 `OnGlError`。`OnLog` ≤WARN 有损(覆盖最旧 + `eventDropped`);≥ERROR 无损,加入触发 `eventRingFull` + 停止 apply 的语义事件集,每秒速率限制器。 +- **唯一允许同步 ack 的入口是 `glBufferStorage`**:`ResourceRespecify` 的 `kNeedsAck` 由逐记录谓词收窄到"不可变 **buffer**"(D-A2),纹理 OOM 在 monolith 里本来就推迟到 sync 时刻。respecify 带逐 level 作用域与"只换元数据"的形式,不误丢已接受的待上传。 +- 其余错误一律晚到,走有序的 `OnGlError`。 ### 8.4 唯一的新停顿类:server 发起的纹理重铸拉取(D-B6) -server 不保留纹素,三个原因会要求重发已发过的 level:`RequireImageBindableStorage` 的 re-dirty、整格式再生、view 源重铸。四条缓解:(1) **预防主因**:`ResourceCreate/Respecify` 一直携带 `ImageBindableHint`;(2) **拉取异步**:server 发 `OnTexturePullRequest` 并把 twin 标 not-ready,阻塞的是 apply 线程不是应用线程;(3) 有上限的保留默认关(`MOBILEGL_PIPE_TEXEL_RETAIN_MB=0`,`MipmapStorage` 保有每 level 完整 CPU 影子,缓存买的是延迟不是正确性);(4) **显式终止符** `ResourceSubDataComplete`,可携带零个 region(内容只来自渲染 / GPU mip 的 level,client 没有字节;server 带着"已分配但为空"的存储继续)。真实语料上重铸拉取率见 `ROADMAP.md` 开放问题 2(780 个窗口 2 次)。P5b 下这条路径是 `Fatal{UnmigratedEmulation,"texture-remint-pull"}`(三条 trace 的首阻塞),P9 落地。 +server 不保留纹素;image-bindable 重铸、整格式再生、view 源重铸会要求重发 level。缓解:`ImageBindableHint` 预防主因;拉取异步(阻塞 apply 线程,不阻塞应用线程);保留 LRU 默认关(`MOBILEGL_PIPE_TEXEL_RETAIN_MB=0`);显式终止符可带零个 region。真实语料上发生率可忽略(780 个统计窗口 2 次);split 下这条路径今天仍是具名 `Fatal{UnmigratedEmulation, "texture-remint-pull"}`,归 P9。 -### 8.5 XFB scatter 留在 server(本节在 P5c/P5f 之后重写;P3b/P4b espryt D1 更正文本) +### 8.5 XFB scatter 留在 server -Espryt 的 `ScatterCapturedRecords` 是 read-modify-write:`gl_SkipComponents` 的空洞必须保留目的缓冲原本的内容,所以补丁循环需要「捕获前的字节」。本节原来写的是**搬到 client**——server 把紧密打包的 scratch 经 `OnBufferWriteback` 推给 client,用 `OnXfbScatterReady` 告知布局,client 跑补丁循环再以 `ResourceSubData` 重发。**树上走的是相反的一条路**,这一段按树更正: - -- **捕获前的字节是 server 自己的 staged shadow**(rule C:拆分后权威影子归 server,`GLESBufferResource::hostBytes`),不是 client 的 `MappedData()`——后者在 apply 线程上是 `Fatal{RoleViolation, "buffer-legacy-arm"}`(CONTRACT-P5C §3.8)。所以 server 有它需要的一切:影子、反射归档里的 varying/stride(`ProgramArchive->Link`),补丁循环原地跑完。 -- **回程只有一条 `OnBufferWriteback`**,载的是**补好的整段**,不是 scratch。`OnXfbScatterReady` 因此是零生产者、零消费者、无 EventKind 的死声明,P3b/P4b espryt D1 slice 3 删除,`kMGPipeCallbackCount` 10 → 9。 -- **该回写按 SEG_EVENT 容量的四分之一切片**(`MGPipeBufferWritebackSliceBytes`,espryt D1 slice 2):字节内联在 SEG_EVENT 记录里,而环形缓冲拒绝任何超过容量一半的记录,所以整段投递在 >128 KiB 时是 `Fatal{EventRingOverflow}`。切片按序投递,最后一片落地即蕴含之前每一片。 -- **孤儿目标(`HasDefinedContent == 0`)是合法目标**(espryt D1 slice 1):它没有可读的捕获前字节是应用自己声明的,零填充后照常散射并上传,与 monolith 从新鲜 `MappedData()` 读到的逐字节相同。 - -不新增停顿类。 +补丁循环需要"捕获前的字节",而拆分后权威影子归 server(`StagedShadow`),所以 scatter 在 server 原地跑完;回程只有一条 `OnBufferWriteback`,载补好的整段,按 `SEG_EVENT` 容量的四分之一切片(`MGPipeBufferWritebackSliceBytes`),最后一片落地即蕴含之前每一片。孤儿目标(`HasDefinedContent == 0`)零填充后照常散射。`OnXfbScatterReady` 已删除(回调 10 → 9)。落地论证见 [`notes/p34b/README.md`](notes/p34b/README.md)(espryt D1)。 ## 9. 后端状态机改造 ### 9.1 原样不动的东西 -Espryt:三条 persistent-mapped ring 与 `PersistentRing` 算法、buffer pool、fallback-repack 路径、`m_backendColorSlots` 置换表、scratch FBO 及驱动侧影子、全部驱动绑定影子、Adreno / Mali workaround、SPIRV-Cross 会话与 ESSL 重写、驱动 POST 自检、restart 重写与 multi-draw 五档。Magma:`VulkanRenderer` 全部 memo 与 scratch、`PipelineFactory`、`ProgramFactory`、`UniformManager`、五个 `Vk*Manager`、`FrameContext`、`SwapchainObject`、`DynamicStateShadow`、`VertexInputStateFactory` 的 cache 本体、**D18 的节点式容器纪律**。从"不动"里移出的一项:Espryt 的 sub-rect 上传判定与跨步计算(§6)。 - -**字节一致门 G5**(`scripts/p3a_untouched_regions.sh`:pool / 延迟释放 / ring / flush 梯十一函数;`scripts/p4a_untouched_regions.sh`:depth-stencil 采样模拟与格式 caveat 两族 17 区 / 3 文件):这些是"被推送的记录改变了输入、但算法本身不许动"的那一类,字节一致是唯一能证明这点的门;两份脚本都对固定 pin 比较,re-pin 必须同时改两份。仍在 `MG_Backend` 里的前端类型:`g_rawDepthFetchSamplerState`(Espryt 自己为原始深度取样铸的 `SamplerObject`,client 从没见过它),原生化归 P3b/P4b。 - -必须真改的 `MG_State` 类型内部用法(都在 Magma,P7):占位纹理(~120 行木偶戏 → 原生 `VkImage`)、两个内部 shader 烘焙(§7)。 +Espryt 的 persistent ring、buffer pool、fallback-repack、scratch FBO、驱动绑定影子、Adreno / Mali workaround、SPIRV-Cross 会话、restart 重写与 multi-draw 分档;Magma 的 `VulkanRenderer` memo、`PipelineFactory`、`ProgramFactory`、`UniformManager`、五个 `Vk*Manager`、`SwapchainObject`、D18 的节点式容器纪律。"输入变了、算法不许动"的那一类由 **G5 字节一致门**把关(`scripts/p3a_untouched_regions.sh` 十一函数、`scripts/p4a_untouched_regions.sh` 17 区),re-pin 必须两份同改。 ### 9.2 strangler 脚手架:`PipeInputs` + 逐 verb 填充 + poison 世代(P1) ```cpp -// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,不按读点组织 -struct PipeInputs { - const RenderStateParameters& GetRenderStateParameters() const; // 类型与后端今天读到的完全一致 - // … 每个后端真正用到的 GLContext 方法一个访问器(Espryt 32 / Magma 56) -#if MOBILEGL_DEBUG || MOBILEGL_BUILD_DISAGGREGATED - Uint64 m_filledGen[kFieldCount]; // 逐字段"上次填充的 verb 序号" - Uint64 m_currentVerbSerial; -#endif -}; +// MG_Backend/MGPipe/PipeInputs.h —— 按 memo 键组织,访问器类型与后端原来读到的完全一致 #if MOBILEGL_PIPE_PUSH # define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs) #else @@ -317,359 +217,242 @@ struct PipeInputs { #endif ``` -| 阶段 | 改什么 | 证明 | -|---|---|---| -| A 别名(P1) | 机械替换 `MG_State::pGLContext->` → `MGB_CTX->`(277 处)+ 58 行非箭头用法;逐 verb 类填充点填 `gPipeInputs` | `nm --defined-only` 不变;`.text` 差异可逐行归因 | -| B 推送(P2) | tracker 填 `gPipeInputs`,填充器按 `MOBILEGL_PIPE_PUSH` 位图逐字段让位 | `MOBILEGL_PIPE_VERIFY=1`:tracker 再填一份快照版,G4 比对器逐字段每 draw 比一次 | -| C 句柄化(P3a–P4a、P7) | `SharedPtr<前端对象>` 字段 → `MGPipeHandle` + POD 描述符;memo 重键;写回变回调 | 全套门(§13) | - -poison 是**逐 verb 世代**不是位图:每次 verb 递增 `m_currentVerbSerial`,字段被填时记下序号,读取时断言相等(跨 verb 有效的字段显式标 sticky)。读一个当前 verb 未填的字段是 `Fatal{UnmigratedPipeInput, "GetStencilState@DrawVbo"}`。纯度门 grep 的是 `pGLContext` 不是 `pGLContext->`。`FillPoints.def` 里 8 条静态过近似的填充行有意保留(能退役它们的证据只能是动态的:`MOBILEGL_PIPE_POISON_OMIT` 跑遍完整 CTS caselist,P3a 之后未做)。 +三步:A 别名(P1,机械替换 + 逐 verb 类填充点,`nm` 不变)→ B 推送(P2,tracker 填 `gPipeInputs`,verify 构建逐 draw 比对快照版)→ C 句柄化(P3a–P4a、P7,`SharedPtr<前端对象>` → 句柄 + 描述符,写回变回调)。poison 是**逐 verb 世代**:读一个当前 verb 未填的字段是 `Fatal{UnmigratedPipeInput, "@"}`。 ### 9.3 Track V / Track H -Track V(值类型:render state、pixel store、capability 位、stencil/colormask/depthmask/scissor/patch/attrib 默认值、Magma 标量 getter,约 B 类读点的 55%):机械。Track H(对象类型:167 个 `SharedPtr` 点):真活。读点分类(静态):探测变化 ~12%、翻译输入 ~74%、瞬时参数 / 身份键 / 数据字节其余——74% 是翻译输入,"bump 一个版本让 server 自己拉"行不通,值本身必须过去。 +Track V(值类型:render state、pixel store、capability 位、标量)是机械迁移;Track H(167 个 `SharedPtr` 读点)是真活。74% 的读点是翻译输入,所以"bump 一个版本让 server 自己拉"行不通,值本身必须过去。 ### 9.4 残余值块 -P2 发一个**显式临时**调用 `SetResidualValueState(MGPBlobRef)`,payload `ResidualValueBlock`。三条纪律:退役是编译错误(`MGL_RESIDUAL_BLOCK_SIZE` 只降不升,P2 已从 1248 棘轮到 **8**——只剩 `Uint64 CapabilityBits`,P13 变成 `static_assert(sizeof == 0)`);布局逐成员 `offsetof` 断言且 split 下逐字段序列化;`MOBILEGL_PIPE_STATS` 单独计一类字节。 +迁移期临时调用 `SetResidualValueState`(`ResidualValueBlock`):尺寸只降不升(1248 → 8,只剩 `CapabilityBits`),P13 变成 `static_assert(sizeof == 0)`;布局逐成员 `offsetof` 断言;stats 单独计字节。 -### 9.5 21 条身份 memo 的重键 +### 9.5 身份 memo 的重键 -统一事实:每个进入 memo 键的版本计数器要么是回绕 `Uint16`,要么根本不会被它害怕的那个 mutation bump;身份比较是堵回绕洞的补丁。`{slot, gen}` + 显式 destroy 让 **11 条直接删除**(registry 的同址 `weak_ptr` + GC ×6、`TwinLookupMemo` ×3 + `OwnerEquals`、`SetBackendStateMemo`、`VkTextureManager::TextureIdentity` 存活探测、`ConvertedVertexStreamKey` 的 `sourcePin`……),**2 条** server 删除但去抖搬到 client(§5.4),**7 条重键**成更便宜的比较(`StampSyncedFBO` 四元组 → `ContentHash` + server 私有 `attachmentRemintEpoch`;`ResolvedTextureBindingMemo` 9 键 → `(shaderCso.slot, viewSetSerial)`;`SetupDrawSnapshot` 的 ~14 探测字段 → 三个 handle + 两个 server 纪元 + dirty mask;`VertexInputStateFactory::ComputeHash` 里的 lifetimeId → `gen` 混进 server 侧每个 content hash),**1 条**(D18)原样不动。P2 付了 11 条删除与 2 条重键,P3a 付了 buffer/VAO 的重键(`MEASUREMENTS.md` §4.3)。 +`{slot, gen}` + 显式 destroy 让 21 条身份 memo 中 **11 条直接删除**、2 条的去抖搬到 client(§5.4)、7 条重键成更便宜的比较、1 条(D18)不动。统一事实:进 memo 键的版本计数器要么会回绕,要么根本不被它害怕的 mutation bump,身份比较是堵洞的补丁。 ### 9.6 A/B 与口径收窄 -`MOBILEGL_PIPE_PUSH` 子系统位图在阶段 B 是真正的旧-vs-新 A/B;阶段 C 之后不是——位清零时后端仍跑重键后的 memo 代码。对策:**编译期** `MOBILEGL_PIPE_LEGACY_MEMOS`(默认 ON)保留 pre-handle 臂活在同一个 `PipeInputs` 接口之下,随 pull 路径在 P13 退役。 - -- buffer 家族的 pre-handle 臂是 `Ops_*` 表加 `g_glesBufferBackendOps`,无条件编译;VAO twin 与 P4a 六种 twin 的前端读取臂在 `MOBILEGL_PIPE_LEGACY_MEMOS` 之下,位清零 + `LEGACY_MEMOS=0` 是没有任何臂的配置,arm resolver **响亮地**报 `Fatal{PipeLegacyMemosDisabled}` 而不是静默。 -- 位依赖两侧都拒:服务端在各族 arm resolver 里拒绝并跑旧臂,客户端在 `PipeFill.cpp` 的族门里**根本不发射**——只在服务端拒会出现"客户端已按 acceptance 清了 dirty、服务端却走旧臂"的丢上传。同一族门上挂着**消费者条件**:没有任何后端注册 `MGPipeResourceOps` 时 P4a 四族一条不发(Magma 就是这种情形,P7 之前)。 -- 退役的 twin 成员(VAO twin 的五个同步 memo、`g_pendingFetchBaseInstance` 与 `ScopedFetchBaseInstance`、D-K 那组)都**仍在 pull 构建里编译**——真删会移走 pull 符号或改 `sizeof`,G1 的 0/0/0/0 就是这条的度量。 - -P13:删 `SnapshotFromGLContext()` 的非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`;**保留 `MOBILEGL_PIPE_VERIFY`**(D-B5,verify 构建永不出货);三道纯度门在非 verify 构建上转绿。 +`MOBILEGL_PIPE_PUSH` 子系统位图在阶段 B 是真 A/B;阶段 C 之后位清零时后端仍跑重键后的代码,所以**编译期** `MOBILEGL_PIPE_LEGACY_MEMOS`(默认 ON)保留 pre-handle 臂,随 pull 路径在 P13 退役。位依赖两侧都拒(客户端族门里**根本不发射**,否则会"客户端已清 dirty、服务端却走旧臂"而丢上传);没有消费者的后端一条不发。退役的 twin 成员仍在 pull 构建里编译——真删会动 G1。 ## 10. server 侧 ### 10.1 对象表与 applier -- `MG_Remote/Server/PipeApplier`:解码 → 更新对象表与 `PipeInputs` → 调后端函数指针。server 不持有任何 buffer 的完整副本、不持有纹素、不持有前端对象图;debug 断言任何传输下都不得有 `SharedPtr` 或裸前端指针跨过 applier 边界。`InProcessTransport` 走与 spawn **完全相同**的 G3 编解码路径,只在门铃/拷贝机制上不同。 -- 每 context 一份 working `RenderStateParameters`(§5.3)。 +`MG_Remote/Server/PipeApplier`:解码 → 更新对象表与 `PipeInputs` → 调后端。server 不持有 buffer 的完整副本(只有 staged shadow)、不持有前端对象图;任何传输下都不得有 `SharedPtr` 或裸前端指针跨过 applier 边界(P5c 角色守卫、P5e 规则 F)。`InProcessTransport` 与 spawn 走完全相同的编解码路径。 ### 10.2 monolith 侧的净收益 -即使 IPC 永不上线:复用地址 ABA 一整类不可表达;FBO → program 排序 hazard 消失;`SwapchainObject` 写 `MG_Impl` 的分层倒置消失;一次 glslang 编译离开启动路径;`inproc` = 渲染线程;`MG_Test` 的 mock 后端变成 MGPipe recorder(§13.3)。monolith 净代码量是**增加**的,所以 monolith 论据是逐线程 CPU 数字,不是删除行数。 +即使 IPC 永不上线:复用地址 ABA 一整类不可表达;FBO → program 排序 hazard 消失;`SwapchainObject` 写 `MG_Impl` 的分层倒置消失;`inproc` = 渲染线程。monolith 净代码量是增加的,论据只看逐线程 CPU。 -### 10.3 索引宿主镜像(`Server/IndexHostMirror`,P8) +### 10.3 索引宿主镜像(`Server/IndexHostMirror`,P8,待重裁) -覆盖 `BindMask & ELEMENT_ARRAY` 的资源,且仅当 `kCapNeedsHostIndexBytes`。由 server 本来就要收的 `ResourceCreate/Respecify/SubData` 流增量维护:零额外线上流量、零 round trip;GPU 写者对镜像的影响由 `OnGpuWritten` 收窄集在 server 本地判定。预算 `MOBILEGL_PIPE_INDEX_MIRROR_MB`(默认 64),超预算时该 buffer 退化为逐 draw 经 `MGHostSpan` 传送(计入 `index-bytes-shipped`)。必须是它:`kMaxRestartRewriteBytes` = 64 MiB 是默认 `SEG_STAGE` 的两倍。它是本设计里唯一的"数据副本"。 +设计:`kCapNeedsHostIndexBytes` 下,server 从本来就要收的 `ResourceCreate/Respecify/SubData` 流增量维护 element-array buffer 的宿主镜像,零额外流量;预算 `MOBILEGL_PIPE_INDEX_MIRROR_MB`(默认 64),超预算退化为逐 draw 经 `MGHostSpan` 传送。**P6 的 a6 审计实测宿主索引 span 无 producer、client 索引已是 owned buffer**,所以 P8 要按真实消费者重新裁定,不照抄本节([`notes/p8/README.md`](notes/p8/README.md))。 ## 11. 传输与数据面(`MobileGL/MG_Remote/`) ### 11.1 段 -| 段 | id | 拥有者 | 默认 | 内容 | -|---|---|---|---|---| -| `SEG_CMD` | 1 | client(server 只读) | 8 MiB,2 的幂 | `RingControl`(4 KiB 页)+ POD 记录 + ≤4 KiB 内联负载 | -| `SEG_STAGE` | 2 | client | 32 MiB | bulk 字节:buffer sub-data、纹理紧密重打包区域、UBO scratch、client 顶点/索引/indirect 数组、multi-draw 参数块、具名 UBO host payload、persistent-map 脏块 | -| `SEG_REPLY` | 3 | server(client 只读) | 16 MiB,8 个 2 MiB slot(ID-47) | readback 像素、buffer writeback、acceptance 答案 | -| `SEG_EVENT` | 4 | server | 256 KiB SPSC ring | 十个回调的事件 + `EvQueryResult/EvFenceSignaled/EvReadbackDone` | -| `SEG_SHADOW[n]` | 5 | client | 每对象 ≥256 KiB(Phase 2) | 零拷贝 buffer/texture shadow | -| `SEG_ADOPT[n]` | 6 | server(client RW) | 每 buffer ≥16 MiB(P11) | 应用直写 GPU 内存 | +| 段 | 拥有者 | 默认 | 内容 | +|---|---|---|---| +| `SEG_CMD` | client(server 只读) | 8 MiB | `RingControl` 页 + POD 记录 + 小内联负载 | +| `SEG_STAGE` | client | 32 MiB | bulk 字节:buffer / 纹理 subdata、UBO、client 数组、persistent-map 脏块 | +| `SEG_REPLY` | server | 16 MiB,8 × 2 MiB slot | readback 像素、acceptance 答案(ID-47) | +| `SEG_EVENT` | server | 256 KiB SPSC ring | 回调事件 | +| `SEG_SHADOW` / `SEG_ADOPT` | client / server | — | 零拷贝 shadow(Phase 2)/ ≥16 MiB 采纳(P11);stream 数据面上具名拒绝 | -`SEG_STAGE` 默认 32 MiB 曾装不下 P5b 目标负载的单次 128 MiB 上传;**内容侧分块已落地(2026-09-20)**——buffer 范围走查(`1e7c372e`)与纹理整宽 slab(`9469d48e`)按 `MGPipeStageChunkBytes()`(默认 segment/4 = 8 MiB)切成多条记录,默认 32 MiB 即可容纳,历史普查 / 设备的显式 `MOBILEGL_IPC_STAGE_MB=256` profile 不再是这两条路径的必需;未接入分片的 record 类型与专用 carrier 仍是 P8 的设计(`ROADMAP.md` 开放问题 11)。 - -创建(`ShmSegment`):Android `ASharedMemory_create`(API 26);桌面 Linux `memfd_create`;其他 POSIX `shm_open`;Windows `CreateFileMappingW`。传递:POSIX `SCM_RIGHTS`(`FdPassing`,专用 `AF_UNIX SOCK_DGRAM` socketpair);Windows 段名走 `SegmentRef`。不进 `SEG_STAGE` 的:restart 重写的整 EBO 与 multi-draw 展平的索引流(走索引镜像)。`SEG_SHADOW` 块的退休:释放的块进 pending 链表,`appliedSeq`(借入 GPU 时间线的 slot 用 `retiredSeq`)越过最后一条引用它的记录后才归还。 +段在同机上由 `ShmSegment` 创建(Android `ASharedMemory`、Linux `memfd`、Windows `CreateFileMappingW`),描述符经 `SCM_RIGHTS` 传递;stream 数据面上两端各持一份同尺寸的私有段(§11.9)。 ### 11.2 `RingControl`(`Ring.h`) -一页 4 KiB,每个争用组各占一条 cache line。**P6 包 `lk` 按写者重组过这一页**(`CONTRACT-P6.md` §8.3):producer 线 `cmdHead` + `submittedSeq`(producer 写、诊断量,`Ring.h` 的 "THE FIVE WATERMARKS" 段写明无人等待它);consumer 线 `cmdAppliedTail / cmdRetiredTail`;`SEG_STAGE` 独立三元组(P5 起故意全零,见 `Ring.h`);**四个 consumer 写的水位收成具名成员 `Progress`**(`Transport::LinkProgress`,`ILink.h`)——`appliedSeq`(释放 `*AppliedTail`)/ `retiredSeq`(释放 staging)/ `completedFrameSerial`(释放 `*RetiredTail` 与 `SEG_ADOPT`)/ `presentAckSerial`,访问写作 `control.Progress.appliedSeq`;最后是 `serverEpoch`、`ringGeneration`、`consumerParked`/`producerParked`、`eventRingFull`、`eventDropped`。**两个 event 标志刻意留在最后这一组**:`eventRingFull` 有两个写者(server `store(1)`、client 每次 drain 一次 `exchange(0)`),放进 `Progress` 会把 client 的 RMW 落到 client 自己自旋读的那条线上。两个 tail 是必须的:P11 之后 server 会**借用** ring slot 而不是再拷一次。游标是单调字节计数、2 的幂掩码、永不重置。 - -记录头 `RingRecordHeader{kind, flags, size}`,kind 0 保留给 wrap 填充;`RingProducer::Reserve` 在记录会跨 wrap 边界时自动发 pad 记录;`RingConsumer::Pop` 拒绝不可能的头并置 corrupt → `Fatal{ProtocolCorruption}`;`HardDrainRing` 只在两侧静默且 ring 全空时 bump generation。 +一页 4 KiB,每个争用组一条 cache line,按写者分组(P6 lk):producer 线 `cmdHead` / `submittedSeq`;consumer 线两个 tail;四个 consumer 写的水位收成 `Progress`(`appliedSeq` / `retiredSeq` / `completedFrameSerial` / `presentAckSerial`);最后是 epoch、generation、park 标志与 `eventRingFull` / `eventDropped`。游标是单调字节计数、2 的幂掩码、永不重置;pad 记录处理 wrap;不可能的头 → `Fatal{ProtocolCorruption}`。 ### 11.3 双向 doorbell(`Doorbell.h`) -- client → server:consumer 自旋 → 置 `consumerParked=1` → 阻塞;producer release-store `cmdHead` 之后仅当 `consumerParked` 时敲。 -- server → client:client 在**任何**等待先自旋 `MOBILEGL_IPC_SPIN_US`(默认 50 µs)→ 置 `producerParked=1` → 阻塞;server 在 release-store 任何 watermark 之后仅当 `producerParked` 时敲。没有第二个方向,每处 client 等待都退化成跨进程自旋一条 cache line。 -- 两个实现:`CondVarDoorbell`(`inproc`,带 `Kill()` 死亡态让 `Shutdown` 能 join 一个 parked 的等待者)与 `SocketDoorbell`(`spawn`,一字节;对端关闭时 `POLLIN|POLLHUP` + `recv()==0` 是死亡检测)。丢失唤醒窗口由两个 `seq_cst` fence 关闭。 +两个方向都是"先自旋、再置 parked、再阻塞",发布方仅当对端 parked 时敲;丢失唤醒由两个 `seq_cst` fence 关闭。`CondVarDoorbell`(同进程,带 `Kill()`)与 `SocketDoorbell`(跨进程,对端关闭即死亡检测)。stream 数据面上门铃是本地条件变量,由读线程敲。 ### 11.4 控制面(`protocol.fbs`、`Framing.h`、`ITransport.h`) -- 一份 schema,两种用法:热路径 → FlatBuffers `struct` 直接进 ring(即 G3 生成的记录,与 `MGPipeTypes.h` 的 POD 逐条 `static_assert` 对齐);罕见/变长/需演进 → `table` 走 CTRL socket(`CtrlMsg`:`Hello`、`Welcome`、`CapsSnapshot`、`SurfaceOp/SurfaceReply`、`ResyncRequest/Done`、`AuxRequest`、`Fatal`、`LogLine`),`file_identifier "MGLC"`,union tag 只追加。 -- `protocol_generated.h` 提交进树,`scripts/gen_protocol.py` 再生成(只用 `MOBILEGL_FLATC_EXECUTABLE` 或 pinned submodule 构建的 flatc),CI `flatc-check` 重生成并 diff;**codegen 绝不进默认构建图**。 -- 封帧 `[u32 'MGLF'][u32 len][payload]`,64 MiB 上限,**读时校验**:坏 magic / 超长长度立即 latch 失败并报 `MOBILEGL_ERR_PROTOCOL_MISMATCH`;接收缓冲不足返回所需大小并保留消息。 -- `ITransport`:`SendFrame / ReceiveFrame / PeekFrameSize / ShareFd / ReceiveFd / Shutdown / Role`;热路径完全绕过它。`WireLog.h` 是 `Transport/` 唯一的日志入口(纯度门 A)。`mg_protocol_base.h`:纯 C 的结果码 / span / `ShmRegion` 词汇,structSize-first 版本纪律。 +- 一份 FlatBuffers schema:热路径是 `struct` 直接进 ring(与 POD 逐条 `static_assert` 对齐);罕见 / 变长 / 需演进的走控制连接的 `CtrlMsg`(`Hello`、`Welcome`、`Refuse`、`CapsSnapshot`、`SurfaceOp/SurfaceReply`、`SessionFault`、`LogLine` …),union tag 只追加,控制修订号钉在 `sha256(protocol.fbs)` 上。 +- 生成物提交进树,CI `flatc-check` 重生成并 diff;codegen 不进默认构建图。 +- 封帧 `[u32 'MGLF'][u32 len][payload]`,64 MiB 上限,读时校验,坏帧立即闩住失败。 ### 11.5 WAR 危害、拷贝账与背压 -Phase 1(P5–P8):GL 调用时刻把字节拷进 ring slot,slot 到 `stageAppliedTail` 越过它为止不可变,危害按构造消除;代价一次 memcpy。Phase 2(shadow-in-shm,零拷贝):≥256 KiB 的 shadow 分配在 `SEG_SHADOW`,`ResourceSubData` 只带 `{seg, offset, size}`,WAR 用 per-shadow 64 KiB 块发送水位;必须整段 `#if MOBILEGL_BUILD_DISAGGREGATED` 包裹。 - -| 路径 | monolith | Phase 1 | Phase 2 | -|---|---|---|---| -| `glBufferSubData` → shadow store | 2 | 3 | 2 | -| `glBufferSubData` → adopted store(P11) | 2 | 2 | 2 | -| `glMapBufferRange(WRITE)`+unmap | 3 | 4 | 3 | -| persistent coherent map 推送(§12) | 0 | 1/发射点 | 1/发射点(精确块) | -| `glTexSubImage` | 2 | 2 | 2 | -| 全局 UBO / draw | 1 | 2 | 1 | -| adopted ≥16 MiB(P11) | 0 | 0 | 0 | - -server 没有第二份 `BufferObject`,不存在"staging → server 侧 shadow"这次中间拷贝。分配与背压:逐字移植 `PersistentRing`;分配失败升级:扩容 → 对最老未 retire 批次有界等待(走 `producerParked` doorbell)→ 硬 `Drain` + `ringGeneration` bump;硬 drain 后恢复便宜:tracker 把全部 dirty 位置为"必须重推"。P5 落地形状:staging 与 command ring 的等待都以未退休发布记录的真实退休为条件(`ringwaits` 只计真正阻塞的分配),超大单记录仍拒绝。 +Phase 1(今天):GL 调用时刻把字节拷进 stage slot,slot 到 apply 越过它为止不可变,危害按构造消除,代价一次 memcpy;Phase 2(`SEG_SHADOW` 零拷贝)未做。server 没有第二份 `BufferObject`,不存在中间拷贝。分配失败升级:扩容 → 对最老未退休批次有界等待 → 硬 drain + generation bump(之后 tracker 全部重推)。 ### 11.6 publish、序号与 credit -- 不设"records ≥ 64 KiB"一类阈值。规则:每条记录(或每 8–16 条摊销)release-store `cmdHead`,仅当 `consumerParked` 时敲门铃。 -- 显式门铃点:`present`、任何 `kNeedsAck` 请求、`eglMakeCurrent`、`glFlush`、`SEG_STAGE` 余量 < 1/4、**轮询类入口**(`glClientWaitSync` 任意 timeout、`glGetSynciv(GL_SYNC_STATUS)`、`glGetQueryObject*(AVAILABLE|NO_WAIT)`);带 `GL_SYNC_FLUSH_COMMANDS_BIT` 无条件 publish。饥饿升级:同一 handle 连续 N 次(`MOBILEGL_IPC_POLL_ESCALATE`,默认 64)本地回答"未就绪"而 watermark 毫无移动 → 升级为一次阻塞 round trip(P10)。 -- `glFlush` 保持纯 no-op。**`glFinish` 在 P5e 之后不再是**:wire 上仍然没有它的记录,但"至此发出的命令都已完成"是一条 run-ahead 队列能违背的承诺,所以它变成 `WaitForApplyToCatchUp`——等到 `appliedSeq` 追上最后一条发布的记录,再排空反向通道(`CONTRACT-P5E.md` §2.5)。lockstep 下它照旧什么都不做,因为那里 client 本来就把自己发出的每条命令都等完了。 -- seq = 记录序数;两个互相独立的窗口:字节 credit 与 present credit(`presentsSent - presentAckSerial >= MOBILEGL_IPC_PRESENT_CREDIT` 时 `eglSwapBuffers` 阻塞)。server 不发 credit 消息,consumer 每 64 条记录更新一次 `appliedSeq`。 -- **P5e:等待规则由生成的 `WaitClass` 列决定,不再由 call class 决定。**`MGPipeWaitClassFor(op)`(`PipeCalls.def` 的第五列)把每一行分成 `kWaitReply` / `kWaitPresent` / `kWaitApplied` / `kWaitNone`,`EmitAndWaitTails` 在 `RunAheadArmed()` 为真时照这一列走:`kWaitNone` 发布即返回,`kWaitApplied` 等一次 apply 再放掉 fill 的四个 O 类 pin,`kWaitPresent` 的等待已经在 `EmitPresent` 编码之前付过(下条)。它**替换**而不是叠加 `MOBILEGL_IPC_BATCH_WAITS` 的跳过规则:batch 读的是 call class,这一列读的是"这条记录的 apply 会读什么",而 server 侧的 `MGPipeBarriered` 读的是后者——两条规则并存就是线两端对"client 现在是否被 park 着"给出不同答案。 -- **强制等待点**(§2.5):`MapBuffer(READ)` / `GetBufferSubData` / `CopyBufferSubData` 源走 `SyncGpuWrites`(reply 行,不变);`glFinish` 如上;每一个 `Server*` EGL forwarder 之前等一次——control mailbox 是在两批 drain **之间**泵的,make-current 落在两条 run-ahead 记录中间不是画错一个像素而是全错。`ServerSwapEGLBuffers` 不在此列:present 就是那次 swap。`glGetError` 的放宽也在这里具名:run-ahead verb 的 `kEventGlError` 在下一个排空点被看到,即最多晚一个 present credit。 +- 每条记录 release-store `cmdHead`,仅当 consumer parked 时敲门铃;显式门铃点:`present`、`kNeedsAck`、`eglMakeCurrent`、`glFlush`、stage 余量不足、轮询类入口。 +- seq = 记录序数;字节 credit 与 present credit 两个独立窗口。 +- **等待规则由 `WaitClass` 列决定(P5e)**:`kWaitReply` / `kWaitPresent` / `kWaitApplied` / `kWaitNone`;run-ahead 武装时 `kWaitNone` 发布即返回。barriered 谓词(`MGPipeBarriered`)client 与 server 用同一个函数、同一份数据算。 +- 强制等待点:`MapBuffer(READ)` / `GetBufferSubData` 源走 `SyncGpuWrites`;`glFinish` = 等 `appliedSeq` 追上再排空反向通道;每个 `Server*` EGL forwarder 之前等一次。`glGetError` 最多晚一个 present credit。 -### 11.7 事件回传与溢出(P9 定策略,P5e 落地) +### 11.7 事件回传与溢出 -`SEG_EVENT` 承载十个回调加回读完成通知。client 排空点:`glGetError`、`glGetQueryObject*`、`glClientWaitSync`、`glGetSynciv`、`eglSwapBuffers`、`glMapBuffer*`/`glGetBufferSubData`/`glCopyBufferSubData`,以及每一次等待循环的每一轮。溢出策略(修一个双向死锁):`EvLogLine` ≤WARN 有损;语义承载事件无损——ring 满时 server 置 `eventRingFull=1`、**在记录边界停止 apply**、敲反向门铃,client 排空后清标志并敲正向门铃。故障注入:client 被 credit 阻塞时灌满 `SEG_EVENT`;日志洪泛下注入一次 link 失败,那行 ERROR 必须出现且两侧恢复。 - -**P5e 把上面这段从计划变成代码(`CONTRACT-P5E.md` §2.6)。**`Fatal{EventRingOverflow}` 的前提是"client 每个 verb 都排空,所以 256 KiB 满环是没有工作负载会产生的突发";run-ahead 的 client 在自己的等待点排空,稳态上相隔一个 present,满环因此是普通积压,为它中止等于把反向通道的容量变成工作负载上限。现在 producer 改为阻塞:发布已在环里的部分、敲 client 门铃、park 在自己的 bell 上等标志清掉、重试。两条具名拒绝留下,而且是**不可能**而非**繁忙**的情形——单条事件大于半个环(再怎么排空也放不下),以及 30 秒都等不到排空(对端不在跑)。不发布 `kCapRunAheadApply` 的 server 原样保留 P5C 的 Fatal。 -死锁论证要两半才成立,所以两半都在:`EventRingConsumer::Drained()` **清标志的同时敲门铃**(清了不敲就是正向那条"先 publish 再敲"专门在防的丢唤醒),而 `SessionProducer::WaitForAppliedOrEventBacklog` / `WaitForPresentAckOrEventBacklog` 让 client 的每一次 park 都能被 `eventRingFull` 打断——于是"等着 client 排空的 server" 总能叫醒那个 client。`ServerLoop` 的 park 条件因此多一条:环满时等待中的记录**还不是**工作,因为 apply 它只会让 producer 停在半路;Stop 与 Control 仍然优先,拆机不被一个不排空的 client 拖住。 +client 在 `glGetError`、query / sync 查询、`eglSwapBuffers`、buffer 回读与每轮等待循环中排空 `SEG_EVENT`。日志有损;语义事件无损:环满时 server 置 `eventRingFull`、在记录边界停 apply、敲 client,client 排空后清标志并敲回(两半都在,才不死锁)。run-ahead 下 producer 阻塞等排空,整笔预算 `MOBILEGL_IPC_EVENT_WAIT_MS`;client 不排空 / 已走 / 会话停止 → `ReverseChannelForfeit{…}` 弃投并闩住(Ph PH-6),而不是 `Fatal` 打掉 server。不发布 `kCapRunAheadApply` 的 server(lockstep 臂)保留 P5c 的 Fatal。 ### 11.8 fence 与无 present 负载(P10) -fence 完成度必须来自**真的逐 fence 退休**,不是 present 水位(DirectGLES 的 `g_completedFrameSerial` 只在 `Present()` 里前进,帧中 fence 会退化成帧计数推断——`DirectVulkan.cpp` 写明这是被修掉的 bug)。无 present 循环下 `retiredTail` 会饿死:DirectGLES 的 server 加**非 present fence tick**(距上次 `Present` 超过 8 ms 或每 4096 条已 apply 记录插一个 `glFenceSync`);P8 加一个无 present 的 split 用例。P5b 已把五条 sync 槽搬上 apply 线程(§17.5)。 +fence 完成度必须来自真的逐 fence 退休,不是 present 水位(DirectGLES 的 `g_completedFrameSerial` 今天只在 `Present` 前进);无 present 循环需要 server 插非 present fence tick。Magma 那半已随 run-ahead 落地。 -### 11.9 传输栈的两根轴(P6.5,2026-09-22 定) +### 11.9 传输栈的两根轴(P6.5) -终局:client 与 server 可以在不同机器、不同 OS / 架构上,经 TCP 连接;同机 `spawn` 保留。为此传输栈拆成**两根独立可选、握手协商、可混搭**的轴,上层只见两个接口: +终局(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接,同机 `spawn` 保留。传输栈拆成**两根独立可选、握手协商、可混搭**的轴,上层只见两个接口: -| 轴 | 接口 | 实现 | 选项 | +| 轴 | 接口 | 实现 | 选项(`MOBILEGL_IPC_CONTROL` / `MOBILEGL_IPC_DATA`) | |---|---|---|---| -| **控制面** | `ITransport`(`Transport/ITransport.h`,只承载 FlatBuffers 控制帧,**不再承载描述符传递**) | `SocketTransport` | `fork`(继承 fd,今天的 spawn)、`unix:`(sm 的独立启动)、`tcp://host:port`(跨机;同机也可用) | -| **数据面** | `ILink`(`Transport/ILink.h`,热路径:记录预留 / 提交、watermark、reply、event、span 解析) | `ShmLink`(共享段 + `RingControl` 页 + 门铃)、`StreamLink`(分块封帧、watermark 变消息、reply 变自带 seq 的消息、发送窗口) | `shm`、`stream`、`auto` | +| 控制面 | `ITransport`(只承载控制帧,不承载描述符) | `SocketTransport` | `fork`(继承 fd)、`unix:`、`tcp://host:port` | +| 数据面 | `ILink`(记录预留 / 提交、水位、reply、event、span 解析) | `ShmLink`(共享段 + `RingControl` + 门铃)、`StreamLink`(分块封帧,水位与 reply 变消息,发送窗口) | `shm`、`stream`、`auto` | -规则: +- 配对在会话建立时选定一次;`LinkTerms`(数据面、窗口尺寸、`maxReplyBytes` …)由 **server 陈述**,client 请求可被钳制。 +- 共享段交付与控制面解耦:`ShmLink` 自带 AF_UNIX aux 汇合名,在 `Welcome` 里公布,所以同机"TCP 控制 + 共享段数据"是合法配对。`auto` 的判据是**段实际交付成功**,回落到 `stream` 必须具名进日志。 +- **上层零链路种类分支**:`ShmLink` / `StreamLink` / `RingControl` 等只出现在 `Transport/` 与其测试(纯度 grep 门);角色谓词回答"谁在哪个进程",不回答"用什么链路"。 +- 两端是不同二进制:`wireFingerprint`(`PipeFields.def` 派生的逐成员布局摘要 + 目录摘要 + 字节序 / 指针宽度)永远比较;`buildFingerprint` 只在 `Dial == Fork`(或 `MOBILEGL_IPC_REQUIRE_SAME_BUILD=1`)下比较;不一致是 `Refuse`,不是 abort。 +- TCP 上每条 `kWaitReply` 就是一个 RTT、`SEG_STAGE` 字节对的是 30–100 MB/s 的链路:这两列从"记录项"变成可行性数(开放问题 19、20)。 -- **配对在会话建立时选定一次,之后不变**(缝是 setup-time 的,`CONTRACT-P6.md` §8.2)。`Hello` / `Welcome` 里的 `LinkTerms{dataPlane, delivery, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 由 **server 陈述**尺寸、client 请求可被钳制。 -- **共享段的交付与控制面解耦**:`ShmLink` 自带一个 AF_UNIX aux 汇合名,在 `Welcome` 里公布,`SCM_RIGHTS` 只走它。于是同机上"控制面 TCP + 数据面共享段"是合法配对,控制面走什么与数据面走什么互不牵连;`ITransport` 收窄为纯控制帧(`auxFd = -1` 已是"这条链不传 fd"的诚实答案)。 -- **`auto` 的判据是段实际交付成功**——client 连 aux、收到 fd、`Adopt` 通过 `fstat` 校验——而不是看对端地址是不是 loopback。回落到 `stream` 必须具名进日志与 stats 行(`ARM` 证明的形状,ID-124),不能静默降级。 -- **上层零链路种类分支**:`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder`、`ClientSession.cpp` 的 `kSegEvent` 挂载、`ResourceTracker.h` 的 writeback 解析都只经 `ILink`;`ShmLink` / `StreamLink` / `RingControl` / `ReplySlotPool` / `EventRingConsumer` / `m_shm.` 只允许出现在 `Transport/` 与其测试(lk 的 grep 门加宽)。角色 / 拓扑谓词(`MGPipeSplitActive()`、`MGPipeBlocksAreDistinct()`、`MGPipeBackendIsLocal()`)回答的是"谁在哪个进程",不是"用的什么链路"。 -- **门铃归数据面**:共享段上是 futex / condvar(同内核跨进程可用),stream 上是消息本身;控制面不参与热路径。 -- **跨机意味着两端是不同二进制**:`wireFingerprint`(`PipeFields.def` 派生的逐成员布局摘要 + 目录摘要 + 字节序 / 指针宽度)永远比较,`buildFingerprint` 只在 `Dial == Fork` 下比较;不一致是 `Refuse`,不是 abort。 -- 配置面(命名待 P6.5 定):`MOBILEGL_TRANSPORT` 仍是**拓扑**(monolith / inproc / spawn,G1 相关);新增 `MOBILEGL_IPC_CONTROL=fork|unix:|tcp://host:port`(取代 `MOBILEGL_IPC_SERVER_PATH` 的角色)与 `MOBILEGL_IPC_DATA=auto|shm|stream`。 -- **代价要量**:`ILink::ReserveRecord` 在 VD32 下是 ~850–3550 次/帧的虚调用,客户端线程已 ~94% 占用;`lk` 的门对 inproc 结果与逐线程 CPU 逐字不变,P6.5 再加 2×2 矩阵车道({unix, tcp} × {shm, stream})与混搭对 (unix + shm) 的逐线程 CPU 差。 - -TCP 上的两个数量级差别(RTT 毫秒级、带宽 30–100 MB/s)使 §13.1 的每一条 `kWaitReply` 行和 §14 的 `SEG_STAGE` 预算从"记录项"变成"可行性数";见 `ROADMAP.md` P6.5 行的必测数与开放问题 19–23。 +第一波(全 TCP 控制 + stream 数据)已落地;同机混搭(nd `auto` / sd)是第二波。细节 [`notes/p65/README.md`](notes/p65/README.md),契约 `MobileGL/MG_Remote/CONTRACT-P65.md`。 ## 12. persistent map 与 ≥16 MiB 采纳 -`AcquirePersistentMap` 是永久的地址空间捐赠(返回 host-visible coherent 指针;≥16 MiB 可变 store 由 `TryAdoptLargeStorage` 自动走到,实测 MC 26.3 p99 163→21 ms、40→115 fps、省 ~400 MB)。**整个 monolith 改造期一动不动**(D-B4)。 +`AcquirePersistentMap` 是永久的地址空间捐赠(≥16 MiB 可变 store 自动采纳,MC 26.3 p99 163 → 21 ms),整个 monolith 改造期不动(D-B4)。拆分下三档由 POST 探针选择(spike B 实测): -三档,由运行时 POST 探针选择,spike B 已在两台设备上给出答案(`MEASUREMENTS.md` §1.2): - -| 档 | 形态 | 实测 | +| 档 | 形态 | 结论 | |---|---|---| -| **T0 — server 导入 client 分配**(P11 主攻) | client 分配 `AHardwareBuffer` BLOB,socket 交接;server 以 `VK_ANDROID_external_memory_android_hardware_buffer`(Magma)或 `EGL_ANDROID_get_native_client_buffer` + `glBufferStorageExternalEXT`(Espryt)导入 | 唯一在两台设备、两个后端上都成立的完整读写档 | -| T1 — server 导出自己的映射 | `VK_KHR_external_memory_fd` opaque fd | 只有 Adreno 的 Vulkan 路径可用;每次存储定义一次 round trip | -| T3 — host pointer 导入 | `VK_EXT_external_memory_host` | Adreno 无扩展;Mali 只读 | -| T2 — 拒绝(永久正确回退) | `AcquirePersistentMap` 返回 `nullptr` | 此档下 client 侧推送强制;**P5 split 使用的档**(`MOBILEGL_IPC_ADOPT_TIER=2`) | +| T0 | client 分配 `AHardwareBuffer` BLOB,server 导入(Vulkan / GLES) | 唯一在两台设备、两个后端上都完整读写的档(P11 主攻) | +| T1 | server 导出 opaque fd | 仅 Adreno 的 Vulkan 路径 | +| T2 | 拒绝,client 侧推送 | 永久正确回退;**今天 split 用的档**(`MOBILEGL_IPC_ADOPT_TIER=2`),stream 数据面上强制 | -`map-persistent-roundtrips`(`mpr`)数的是**每一次 `MapPersistent` 发射,铸成还是拒绝都算**——按"真正发生的往返"定义在 monolith 下按构造恒为 0,永远红不了;按获取尝试计数两种模式下数字相同、在 monolith 下就可断言(门:`StorageBufferRegrowScenario`、`LargeArenaAdoptionScenario`)。 - -**client 侧 persistent map 推送三件套**(T2 档强制,P5 落地):(1) 不做 map/unmap 命令对,`ResourceRespecify/SubData` 的 payload 带 `hasLiveHostWrites` 位;(2) 块粒度脏块推送:tracker 维护 `m_livePersistentMaps`,在每个 validate 点对本次操作可达的每个这类 buffer 按 `MOBILEGL_IPC_PERSISTENT_BLOCK_KB`(默认 64)切块发送——Phase 1 保守版(整个 mapped span 当脏),Phase 2 精确版(shadow-in-shm 的块脏位);(3) 门 `PersistentCoherentMapScenario`(map PERSISTENT|WRITE|COHERENT、写、不做任何其它 GL 调用、draw、readback 校验)。R-1(§17.1)的 apply-role producer guard 保证 server 不会重入 client 的 persistent-map producer。`MOBILEGL_COHERENT_AS_FLUSH` 在拆分模式下照常生效。 +T2 下 client 侧推送三件套:不做 map/unmap 命令对(payload 带 `hasLiveHostWrites`);在每个 validate 点按块(`MOBILEGL_IPC_PERSISTENT_BLOCK_KB`,默认 64)推送可达的已映射 buffer,脏页追踪 + 哈希抑制只发变化的块(P5d);门 `PersistentCoherentMapScenario`。`mpr` 数的是每一次 `MapPersistent` 发射(铸成或拒绝都算),所以在 monolith 下也可断言。 ## 13. 回读、roundtrip 清单与验证 ### 13.1 稳态零 roundtrip 与不可避免的阻塞点 -零 round trip:全部 draw/clear/blit/copy/dispatch/barrier/XFB 跨度/bind/CSO/`set_*`/上传/`present`;全部 caps 站点;`glGetError`/`glFinish`/`glFlush`;fence 与 query 的创建及非阻塞轮询;`glGetTexImage`(DirectGLES);`glReadPixels` → pack PBO(fire-and-forget + client 侧 `MarkGpuWritten`,P6+);`glEndTransformFeedback`;`eglSwapBuffers`;`*IndirectCount`;restart/multi-draw。**TCP 终局(2026-09-22)**:每一条 `kWaitReply` 行在跨机链路上就是一次 RTT——`ResourceCreate`、`SetTextureParams`、纹理半边的 `ResourceSubData` 都在此列(`CONTRACT-P5E.md` §2.5)——所以逐帧 `kWaitReply` 次数是 P6.5 的必测数,与本节的 roundtrip 计数器同源;MC 加载区块那一阵的几百次创建在 1–5 ms 一次下是可感知的卡顿,要么减少这一列,要么让它们异步(P9)。 - -不可避免(全部罕见):握手一次;surface 生命周期与首次 `MakeCurrent`+`InitCapabilities` 每 surface 至多一次;`glReadPixels` → 客户内存(像素进 `SEG_REPLY`);`glGetTexImage`(DirectVulkan);GPU-write pending 的 buffer 首次 CPU 读;`glClientWaitSync(timeout>0)`、`GL_QUERY_RESULT` 未完成、`glBeginConditionalRender`(谓词只解析一次);`glBufferStorage` 的 ack;`MapPersistent`(仅 T1);纹理拉取(§8.4);client 侧索引扫描当源 EBO 在 pending 集里;ring/stage 耗尽与 present credit。 - -验收措辞:在全部 trace 用例上发布逐用例的 roundtrip 计数器、纹理拉取计数器、索引镜像字节数;零 timeout 轮询循环必须在有界时间内退出。 +- 零 roundtrip:全部 draw / clear / blit / copy / dispatch / XFB / bind / CSO / `set_*` / 上传 / `present`;全部 caps 站点;`glGetError` / `glFinish` / `glFlush`;fence 与 query 的创建和非阻塞轮询;`eglSwapBuffers`;restart / multi-draw。 +- 不可避免(罕见):握手;surface 生命周期;`glReadPixels` 到客户内存;GPU-write pending 的 buffer 首次 CPU 读;带超时的 sync / query 结果;`glBufferStorage` 的 ack;纹理拉取;ring / stage 耗尽与 present credit。 +- 跨机上每条 `kWaitReply`(`ResourceCreate`、`SetTextureParams`、纹理 `ResourceSubData`)都是一个 RTT:逐帧次数是 P6.5 必测数,减少它是 P9 / P10 的活。 ### 13.2 五部分验证门(取代 monolith 的字节一致门) -1. **接口纯度三道门**:**A 门 include 图**(disaggregated 配置编译 `MG_Backend` 时把 `MG_State/GLState` 从 include 搜索路径移除,`scripts/check_include_closure.py`);**B 门符号** `nm --undefined-only libMobileGLServer.so | grep -E 'MG_State::GLState::|glslang'` 为空;**C 门** `grep -c 'pGLContext' MG_Backend/` == 0。外加 debug 断言"每个后端 memo 键都是 `{slot, gen}`",由 `HandleRecycleScenario` 支撑(重键前必须在至少一个后端上是红的)。 -2. **语义影子比对 `MOBILEGL_PIPE_VERIFY=1`**——决定性的一条:两套状态模型活在同一地址空间,tracker 再用 `SnapshotFromGLContext()` 填一份 `PipeInputs`,G4 比对器逐字段每 draw 比对,打印第一个分歧字段与 draw 序号。第三种 CI 模式(`build-linux-verify` / `integration-verify` / `retrace-verify`),~5–10× 慢,永不出货。**保留模式**:消费即清的组(纹理 dirty rect)verify 时保留清除前的集合并比对发射出去的 `(UnionBox, RegionCount, Regions[])`。活过 P13。 -3. **行为 A/B**:trace 语料在 `{monolith-pull, monolith-push, split}` 下 SSIM ≥ 0.99;`ctest -L integration-gpu` 在 `DirectGLES.` 与 `DirectGLES.Split.`(DirectVulkan 同)之间逐名相同(G2);测试名只增不删(G14);单元全绿;CTS 逐后端 conformance 在 0.5 pp 内(只在五个架构边界与合并 `dev` 之前跑完整 caselist)。`TextureUploadShapeScenario` 把逐纹理逐帧的上传形状录金标比对——+6 ms 悬崖由形状相等把关,SSIM 对它完全不敏感。 -4. **性能**:Redmi `2f7cbe2e` reboot-clean、同热窗口、配对 A/B(`devices/pin-verification-2026-09-07.md`),trace replay `--benchmark` 逐帧 JSON(`frameTimesMs[]` + `frameCpuTimesMs[]`);**指标是逐线程 CPU 时间**,p50 与 p99;tracker 每 draw 的绝对 ns(`DriverBench` T1/T2)与 Blaze3D blend-toggle 微基准单列;关掉 CSO 内容寻址的负面对照。**口径(用户 2026-09-08 起):对着 pull 臂记录,不作阻塞门**;专门的优化阶段排在路线图之后。 -5. **覆盖 + poison + 句柄纪律**:G6 重生成 0 UNMAPPED;`gen_pipe_dirty_surface.py` 0 未映射 mutator;G8 字段归属完备;逐 verb 世代 poison;G7 setter 一致性;`ResidualValueBlock` 棘轮。 +1. **接口纯度**:A 门 include 图(`scripts/check_include_closure.py`)、B 门 server 镜像不引用 `MG_State::GLState` 与 glslang(P6 的 a6 实测 server 仍链 `MG_Impl`,由 `scripts/link_ratchet.py` 的下行棘轮追踪,P13 转绿)、C 门 `MG_Backend` 里零 `pGLContext`;外加"每个后端 memo 键都是 `{slot, gen}`"(`HandleRecycleScenario`)。 +2. **语义影子比对** `MOBILEGL_PIPE_VERIFY=1`:tracker 再用 `SnapshotFromGLContext()` 填一份 `PipeInputs`,G4 逐字段每 draw 比对,打印第一个分歧;verify 构建永不出货,活过 P13。 +3. **行为 A/B**:trace 语料在 monolith-pull / monolith-push / split 下 SSIM ≥ 0.99;`DirectGLES.` 与 `DirectGLES.Split.` 等名集合逐名相同(G2)、测试名只增不删(G14);CTS 逐后端 0.5 pp 内;上传形状金标。 +4. **性能**:Redmi `2f7cbe2e` reboot-clean、同热窗口配对 A/B,指标是**逐线程 CPU 时间**;对着 pull 臂**只记录、不设门**(用户 2026-09-08)。 +5. **覆盖 + poison + 句柄纪律**:G6 0 UNMAPPED、dirty-surface 0 未映射、G8 归属完备、逐 verb poison、G7、残余块棘轮。 -两条幸存的字节级等式:`MOBILEGL_BUILD_DISAGGREGATED=OFF` 时 `nm --defined-only libMobileGL.so | grep MG_Remote` 为空;**G1**:pull 构建的符号集与 `.text` 字节对 P3a 起的基线恒等(`scripts/symbol_report.py --threshold 0`,每阶段 0 增 / 0 删 / 0 resize / 0 重命名)。**每个门必须能因它存在的理由变红**:每个门都带阴性对照并真跑过一次红(R-16);公共 GL 看不见的改白盒断言(`PipeApplyPeek`、`PipeSlotPeek`、`BackendCapsPeek`);`ctest -V` 做拒绝普查是假零(console sink 在发布配置里被编译掉,必须逐用例读自己的日志文件)。 +两条幸存的字节级等式:`MOBILEGL_BUILD_DISAGGREGATED=OFF` 时库里零 `MG_Remote` 符号;**G1** pull 构建的符号集与 `.text` 对基线恒等(每阶段 0 增 / 0 删 / 0 resize / 0 重命名)。**每个门必须能因它存在的理由变红**(R-16):带阴性对照并真跑过一次红;公共 GL 看不见的改白盒断言;拒绝普查逐用例读私有日志(`ctest -V` 是假零)。 ### 13.3 长期语义门:MGPipe recorder(P13) -`MG_Test` 的 mock 后端变成 MGPipe recorder:在一组 fixture 上录下每 draw 的已推送状态,后续构建对比录像。它不依赖 `MG_State`,也给 `tools/trace_replay` 一种记录**已解析**状态的录制格式。它只覆盖推送内容,不覆盖后端对它的解释。 +`MG_Test` 的 mock 后端变成 MGPipe recorder,在一组 fixture 上录每 draw 的已推送状态做金标;它只覆盖推送内容,不覆盖后端对它的解释(开放问题 12)。 ## 14. Present、线程与帧节奏 -- `eglSwapBuffers` → `present{frameSerial}` → publish + 敲门铃 → 返回,除非超出 credit。**`present` 与 `eglSwapBuffers` 严格 1:1**:两个后端的帧边界排空只在 `Present` 内发生,批量会饿死它们。 -- **`MOBILEGL_IPC_PRESENT_CREDIT` 默认 1**(P10 提出,P5e 落地,range 1..8):延迟叠加,server 的 `Present` 末尾已在 `vkWaitForFences` 上等 2–3 帧,credit 2 就是端到端 4–5 帧;只有实测吞吐收益能抵掉延迟代价才调高。Magma 从不注册 `SetSwapInterval`,IPC credit 是它唯一的显式限帧器。 -- **P5e:credit 是 run-ahead 的唯一稳态背压,字节不是**(`CONTRACT-P5E.md` §2.4)。`MGPPresent::FrameSerial` 从"0,由 server 盖自己的帧数"改成**由 client 铸造、1 起算**——一个 credit 只能在付账方能推进的 id 空间里计量,而付账方是 client;`ServerVerbSink::OnPresent` 在 `Present()` 返回之后调 `ServerSession::ReturnPresentCredit(FrameSerial)`,一次 swap 一个 credit(那个函数自 v1 写下以来一直没有生产调用者,这是它的第一个)。credit 1 = client 在 server 应用并交换第 N 帧时发布第 N+1 帧的记录:一帧重叠,最多一帧附加延迟。等待发生在**编码之前**——先编码再 park 会把一次 SEG_CMD 预留持有整整一帧 server 时间。run-ahead server 上 `FrameSerial == 0` 是 `Fatal{ProtocolCorruption, "Present.FrameSerial"}`。 -- 预算(~850 draw + ~0.36 MB pmap/帧):SEG_CMD 每帧 ~70 KB 对 8 MiB,SEG_STAGE ~1 MB/帧对 32 MiB,SEG_REPLY 不变。跨机 TCP 上这 ~1 MB/帧对的是 30–100 MB/s 的链路而不是内存,60 fps 即 60 MB/s,贴着 Wi-Fi 上限(`ROADMAP.md` 开放问题 19、20)。`WaitForCmdSpace` 与 stage bell 是安全网而不是节奏器,两者退出时都排空 SEG_EVENT。stats 行与 wire ledger 多一个 `credit-waits`:credit 1 下它是 0 就说明 server 从来不在帧的关键路径上,接近帧数就说明它一直是。 -- 线程——client:**v1 不加线程**,编码在 GL 线程上直接写 ring;外来线程的 sync/query 读从 `RingControl` 无锁回答,必须发射的少数取 `ctrlMutex` 走 `AuxRequest`(SPSC ring 不允许第二个 producer)。server:`mgl-srv-io`(封帧、`SCM_RIGHTS`、doorbell、CTRL RPC)、`mgl-srv-apply`(**终身持有原生 context**,`MakeCurrent` 的缓存失效风暴变启动期一次性)。 -- **核心放置**:全库无亲和性控制。规则:报总 CPU 工作量差(client tracker + encode + decode + server apply vs monolith `PrepareForDraw`);复用 `ShaderCompilePool` 的大核探测把 `mgl-srv-apply` 绑到大核(`MOBILEGL_IPC_SERVER_AFFINITY`,默认 auto)。 -- 拆机顺序:publish + server 排空并 ack → 停 apply 线程 → 关 transport → client 排空 compile pool → `MobileGL::Destroy()` → 释放 sync/query handle(P5 落地形状见 §17.3)。 +- `eglSwapBuffers` → `present{frameSerial}` → publish + 敲门铃 → 返回,除非超出 credit;**`present` 与 `eglSwapBuffers` 严格 1:1**(后端的帧边界排空只在 `Present` 内发生)。 +- `MOBILEGL_IPC_PRESENT_CREDIT` 默认 1(range 1..8):credit 是 run-ahead 唯一的稳态背压,`FrameSerial` 由 client 铸造、1 起算,server 在 `Present()` 返回后归还 credit;等待发生在**编码之前**。credit > 1 从未量过(P10)。 +- 预算(~850 draw/帧):`SEG_CMD` ~70 KB/帧,`SEG_STAGE` ~1 MB/帧;跨机时这 ~1 MB/帧对着的是 Wi-Fi。 +- 线程:client v1 不加线程,编码在 GL 线程直接写 ring;server 有 io 线程与**终身持有原生 context** 的 `mgl-srv-apply`。全库无有效亲和性控制(Redmi 内核忽略 app 线程的 `sched_setaffinity`)。 +- 拆机顺序:publish + server 排空 → 停 apply 线程 → 关传输 → client 排空编译池 → `MobileGL::Destroy()`(落地形状见 §17)。 -## 15. 进程、EGL 与平台(P6 / P12) +## 15. 进程、EGL 与平台(P6 / P6.5 / P12) ### 15.1 启动与握手 -- server 定位:`MOBILEGL_IPC_SERVER_PATH`(主要)→ `dladdr(&MobileGL::Initialize)` 同目录的 `libMobileGLServer.so`(兜底;集成测试静态链接 `MobileGL_s`、trace replay 的可执行文件不在库目录)。 -- **端点种类(P6.5 ct,2026-09-22)**:`fork`(继承 fd,本节其余各条描述的形状)、`unix:`(sm 的独立启动,`SocketTransport::Listen` / `ConnectTo`,两条连接)、`tcp://host:port`(跨机;同机也可用,此时数据面仍可协商为共享段,§11.9)。TCP keepalive / 心跳失败**由传输层报告为挂断**,进 dl 的 device-lost 闩——它不是 apply 超时,dl 的"绝不取自超时"不因此松动。 -- 启动:`socketpair(AF_UNIX, SOCK_STREAM)` + `fork`/`execve`,fd 3 = socket。无文件系统 socket 路径、无 abstract namespace。 -- **子进程强制 monolith**(修无界 fork 链):spawn 时构造显式 envp 剔除 `MOBILEGL_TRANSPORT` 与全部 `MOBILEGL_IPC_*`;`mobilegl_server_main` 在到达 `Init()` 之前把 `MG_Config::Transport` 硬置为 `Monolith`。两条都做。`MG_Test/Wire` 测试:进程树只多出恰好一个子进程。 -- `Hello{abi, backendType, buildFingerprint, configBlob}` → `Welcome{四个段}`。`configBlob` 转发 client 解析好的 `MG_Config::Features`;`buildFingerprint`(git hash + `PipeCalls.def` hash)不匹配 → `Fatal{AbiMismatch}`;segment 尺寸尚未混入 fingerprint(ID-47 债)。 -- `mobilegl_server_main` 声明为 `extern "C" __attribute__((visibility("default")))`(非 Debug 构建设了 hidden visibility)。 +- 端点:`fork`(`socketpair` + `fork`/`execve`,fd 3 = socket;server 由 `MOBILEGL_IPC_SERVER_PATH` 或 `dladdr` 同目录的 `libMobileGLServer.so` 定位)、`unix:`、`tcp://host:port`。设备侧 supervisor `mobilegl_server_main --serve`:listen → 认证 → 每会话 fork 一个子进程(或进程内线程,P12),同时只服务一个会话,第二个连接 `Refuse{Busy}`。 +- server 进程里 `Transport` 读作 `Spawn`,**不强制 monolith**(两百多处 `Transport != Monolith` 判断靠它选 server 臂;`CONTRACT-P6.md` §3.1 修订了原"子进程强制 monolith"的规则)。防无界 fork 链靠不继承的 `Dial == No`(`MOBILEGL_IPC_DIAL=no`),spawn 时构造的 envp 剔除 `MOBILEGL_TRANSPORT` 与 `MOBILEGL_IPC_*` 作第二道独立保险;server 主程序自设 `MOBILEGL_IPC_ROLE=server`。 +- `Hello`(abi、后端类型、指纹、配置、令牌、`LinkTerms` 请求)→ `Welcome`(server 陈述的尺寸、段 / aux 名、`dataNonce`、真实 pid)或 `Refuse{code, detail, 对端值}`;握手路径零 abort。非 loopback 监听必须有令牌(≥16 字节、常量时间比较、数据面按 `dataNonce` 绑定到已认证的控制连接、fork 前认证;Ph,OQ-21 裁定不做 TLS)。 +- TCP keepalive / 心跳失败**由传输层报告为挂断**,进 device-lost 闩;"闩绝不取自 apply 超时"不因此松动。冷启动期 server 发 `SurfaceProgress`,client 预算这段沉默(控制修订 2)。 -### 15.2 Android(spike A 已证,`MEASUREMENTS.md` §1.1) +### 15.2 Android -- 交付链:APK 唯一可 exec 的位置是 `lib//`,server 以 `add_executable` + `PREFIX "lib"/SUFFIX ".so"` 构建(真 PIE),`RUNTIME_OUTPUT_DIRECTORY` 指到 AGP 收集原生产物的目录(`MOBILEGL_BUILD_SERVER_SPIKE`)。**两台设备上都已证明**:从 `untrusted_app` 进程 `fork`+`execve` 子进程落在同一域、exit 0、零 avc denial。 -- `fork`+`execve` 而非 `posix_spawn`:bionic 从 API 28 才声明后者,minSdk 26。fork 与 execve 之间只做 async-signal-safe 调用。应用进程的 stdout/stderr 是 `/dev/null`:子进程用 marker 文件证明自己活过,exec 被拒的 errno 经 close-on-exec pipe 回传。 -- 生产 server 主体是 ~30 行 stub:`dlopen(libMobileGL.so)` → `dlsym("mobilegl_server_main")`。一份共享库、两个角色。 -- minSdk 26 没有公开 NDK API 能扁平化 `ANativeWindow`。**P5–P11 验证路径无窗口**(pbuffer / `AImageReader`)。~~P12 生产路径:Java `Surface` → Messenger/AIDL → `MobileGLServerService` → JNI `ANativeWindow_fromSurface`~~——按重审 §6 删掉(client 的窗口不跨进程,Rule H)。 -- **P12 上屏路径(2026-09-24,子集,[`notes/p12/INTEGRATOR-DECISIONS-P12.md`](notes/p12/INTEGRATOR-DECISIONS-P12.md))**:窗口是 **server 自己的**。`MobileGLDisplayActivity`(`android:process=":mglwin"`)持有全屏 SurfaceView,并在**自己的进程里**用一个线程跑 TCP server(`mobilegl_server_serve_inprocess`,即 `--serve` 的线程形状:会话串行、会话间重置 latch、后端按进程钉住);`ServerDisplay` 以 acquire/release/几何上调三个钩子持有该窗口,apply 线程以**租约**使用它。client 设 `MOBILEGL_IPC_SURFACE=server` 后**无头**:`eglCreateWindowSurface(NULL)` 发一帧 `WindowKind::ServerOwned`(token 0,控制修订 3),不发 `SetWindowHandle`,reply 带回窗口真实尺寸;client 的 resize 变成对 server 窗口的几何请求(`setFixedSize` / 0/0 = `setSizeFromLayout`),同样以真实尺寸应答。离屏 service(`MobileGLServerService`,fork-per-session)保留,**一台设备同一时刻一个 server**,一个会话一种表面模式(`SurfaceModeMismatch` 具名拒绝);无显示的 server 对 ServerOwned 具名拒绝(`NoServerDisplay`)。 -- **失窗与会话边界(P12)**:`surfaceDestroyed` 有界(3 s)阻塞,直到 apply 线程——在批内每条记录之前也检查——放掉后端表面并以 `Fatal{ServerWindowLost}` 闩会话,之后才返回;server 继续监听。进程内的会话结束手动做进程退出原本白给的清理:Espryt 丢弃全部 twin 及指向它们的纹理/采样器单元影子、忘掉 swap interval,Magma 丢弃进程全局 renderer;进程内 server 停止时不再 drain 仍在推流的 client 的队列;日志只转发当前会话自己线程的行,且不在日志互斥下发送。 -- `HeadlessGL` 的 fork 预检会 fork 一个子进程跑完整 EGL bring-up 然后 `_exit`——拆分模式下那个子进程会 spawn 一个孤儿 server。规则:server 的 EOF 检测**即时且无条件退出**;client 的 socket fd 设成 `_exit` 会确定性关闭的形态;握手有界重试。P6 验收。 -- 通用 env 透传 `--env K=V`(`run_android_retrace_local.py` → intent extra → `trace_replay_core.cpp` 在加载库前 `setenv`)已接进 retrace 通道。 +- 交付链(spike A 已证):APK 唯一可 exec 的位置是 `lib//`,server 以 `add_executable` + `lib*.so` 命名构建;从 `untrusted_app` 进程 `fork`+`execve` 同域、零 avc denial;`posix_spawn` 在 minSdk 26 不可用。一份共享库两个角色(stub `dlopen` + `dlsym("mobilegl_server_main")`)。 +- 离屏 server:前台 `MobileGLServerService`(`:mglsrv`),从 `nativeLibraryDir` exec,fork-per-session,EGL 是 pbuffer / surfaceless。 +- **上屏 server(P12 子集)**:窗口归 **server 自己**(client 的窗口不跨进程)。`MobileGLDisplayActivity`(`:mglwin`)持全屏 SurfaceView,在自己的进程里用一个线程跑 TCP server(会话串行、会话间重置闩、后端按进程钉住);`ServerDisplay` 以 acquire / release / 几何三个钩子持有窗口,apply 线程以**租约**使用它。client 设 `MOBILEGL_IPC_SURFACE=server` 后**无头**:`eglCreateWindowSurface(NULL)` 发一帧 `WindowKind::ServerOwned`(控制修订 3),不发 `SetWindowHandle`,尺寸由 reply 带回,resize 变成对 server 窗口的几何请求。一台设备同一时刻一个 server;一个会话一种表面模式(`SurfaceModeMismatch`);无显示的 server 对 ServerOwned 具名拒绝(`NoServerDisplay`)。 +- 失窗:`surfaceDestroyed` 有界(3 s)阻塞,直到 apply 线程(批内每条记录前也检查)放掉后端表面并以 `Fatal{ServerWindowLost}` 闩住会话;server 继续监听。进程内会话结束时手动做进程退出本来白给的清理(Espryt twin 与单元影子、swap interval、Magma renderer)。 +- `HeadlessGL` 的 fork 预检会产生孤儿 server:server 的 EOF 检测即时且无条件退出。 ### 15.3 Linux / Windows / 崩溃 -- Linux/X11:`Window` 是 XID,`nativeToken:u64` 直接送;Wayland 维持不支持;WSL/CI 永不开窗(`EGL_PLATFORM=surfaceless`)。Windows:`HWND` 进 `nativeToken`,Vulkan 可行,WGL/ANGLE 对外进程 HWND 不受支持 → headless only;transport 默认 named pipe。Windows 机器不是正确性门。macOS 不拆分。**2026-09-22**:TCP 终局下 Windows / 桌面 client 成为真实形态,`pipe:` / `unix:` 的 Windows 具名拒绝由 TCP 替代;"不是正确性门"与"macOS 不拆分"在 client 平台范围(`ROADMAP.md` 开放问题 22)定下前维持。 -- server 死:client 读到 EOF/EPIPE → device-lost 闩锁(GL 调用 no-op、`eglSwapBuffers` 返回 `EGL_FALSE`+`EGL_CONTEXT_LOST`、`glGetGraphicsResetStatus` 返回 `GL_UNKNOWN_CONTEXT_RESET`);`MOBILEGL_IPC_RESPAWN=1` 时重启并全量重推(默认关)。client 死:server 读到 EOF → 立即销毁原生 context 并退出;`MOBILEGL_IPC_IDLE_EXIT_S`(默认 30)只作最后保险。 +- Linux / WSL / CI 永不开窗(`EGL_PLATFORM=surfaceless`);Wayland 不支持。Windows / 桌面 client 在 TCP 终局下是真实形态(Windows 是第二批,开放问题 22);macOS 不拆分;Windows 机器不是正确性门。 +- server 死:client 读到挂断 → **device-lost 闩**(GL 调用 no-op、`eglSwapBuffers` 返回 `EGL_CONTEXT_LOST`、reset status `GL_UNKNOWN_CONTEXT_RESET`);`Session::Fail` 漏斗向对端发 `SessionFault` 帧命名家族(`FatalFamilies.def`)。**不重启**:`MOBILEGL_IPC_RESPAWN` 今天是具名拒绝。client 死:server 读到 EOF 立即销毁 context 并退出(supervisor 继续服务下一连接)。 ## 16. 构建布局 ``` -MobileGL/MG_Pipe/ 永远进构建(monolith 的架构) - PipeCalls.def PipeFields.def Coverage.def FieldOwnership.def FillPoints.def DirtySurface.def - MGPipe.h MGPipeTypes.h MGPipeValueTypes.h MGPipeHandles.h MGPipeHostSpan.h MGPipeCallbacks.h - MGPipeRenderStateSpans.{h,cpp} PipeApply.{h,cpp} PipeRoute.{h,cpp} PipeMutation.h PipeInputsSwitch.h - generated/ PipeTables PipeThunks PipeWire PipeVerify PipeFilled PipeCoverage PipeSpanTable PipeFieldOwnership PipeFillPoints (.inc) -MobileGL/MG_Impl/Pipe/ Tracker.h PipeFill.{h,cpp} SlotAllocator CsoCache CompositeResolver ResourceTracker - SetHashSuppressor {VertexInput,Framebuffer,Texture,Sampler,Image,Program}Emit.h -MobileGL/MG_Backend/MGPipe/ PipeInputs.{h,cpp} -MobileGL/MG_Remote/ 仅 MOBILEGL_BUILD_DISAGGREGATED - CONTRACT-P5.md CONTRACT-P5B.md CapsCodec.{h,cpp} - Protocol/ protocol.fbs generated/protocol_generated.h mg_protocol_base.h - Transport/ ITransport InProcessTransport Framing Ring SessionRings ReplySlot EventRing RoleMemory - ShmSegment(+Posix/Win32) FdPassing Doorbell WireLog SocketTransport [P6] - Wire/ PipeWireCodec.{h,cpp}(记录编解码、WireVerbSink) - Client/ BackendObject_Remote ClientSession EmitTables WireTables SlotCaps CapsMirror - PersistentMapTracker GpuWritePending - Server/ PipeApplier(ServerVerbSink)ServerLoop ServerSession StagedShadow IndexHostMirror ServerMain [P6/P8] +MobileGL/MG_Pipe/ 永远进构建:*.def 目录与表、MGPipe*.h payload / 句柄 / 回调、PipeApply、PipeRoute、generated/*.inc +MobileGL/MG_Impl/Pipe/ Tracker、PipeFill、SlotAllocator、CsoCache、CompositeResolver、ResourceTracker、SetHashSuppressor、*Emit.h +MobileGL/MG_Backend/MGPipe/ PipeInputs.{h,cpp} +MobileGL/MG_Remote/ 仅 MOBILEGL_BUILD_DISAGGREGATED:CONTRACT-*.md、CapsCodec、FatalFamilies.def / FatalFunnel、Handshake.h + Protocol/ protocol.fbs、generated/、SurfaceOpCodec、mg_protocol_base.h + Transport/ ITransport、SocketTransport、InProcessTransport、ILink、ShmLink、StreamLink、Ring、SessionRings、ReplySlot、EventRing、 + Doorbell、ShmSegment(+Posix/Win32)、FdPassing、Framing、AuthToken、ControlInbox、LinkMetrics、WireLog + Wire/ PipeWireCodec(记录编解码、WireVerbSink) + Client/ BackendObject_Remote、ClientSession、EmitTables、WireTables、CapsMirror、PersistentMapTracker、GpuWritePending + Server/ PipeApplier、ServerLoop、ServerSession、ServerMain / ServerEntry / ServerSpawn、PreAuthGate、StagedShadow、StagedTextureStore、 + SurfaceControlFrame、ServerDisplay、InProcessServer、DisplayServerJni ``` -- CMake option:`MOBILEGL_BUILD_DISAGGREGATED`(默认 OFF)追加 `MG_Remote/**` 并定义 `-DMOBILEGL_BUILD_DISAGGREGATED=1`;OFF 时 `MG_Config::Transport` 是 `constexpr Monolith`。`MOBILEGL_BUILD_DISAGGREGATED_INPROC` 隐含前者并加角色隔离:MGPipe 让需要角色分身的进程全局从四个降到两个(pipe 表与 `pActiveBackendObject`),两个角色通过 apply thread 与控制 mailbox 分开,而不是给 1494 个 `pGLContext->` 读点加 TLS。`3rdparty/flatbuffers/include` 缺失时把 option 强制回 OFF。 -- `MobileGLServer`(P6):桌面 `add_executable` 链接 `MobileGL_s`;Android 改名 `lib*.so` 链接共享 `MobileGL`。 -- `MOBILEGL_TRANSPORT = monolith | inproc | spawn | unix: | pipe:`(`ConfigLoader.cpp` 解析;P5 只接受前两个,其余具名拒绝并回落 monolith),免费换来 ctest `ENVIRONMENT` 变体、trace-replay 的 `setenv` 块、FCL 用户 env、plugin APK 的开关表。**split build 不设 `MOBILEGL_TRANSPORT=inproc` 时是 monolith control arm**。 -- 测试接线陷阱:ctest `ENVIRONMENT` 是替换而非追加、`;` 必须转义,必须用 `mgl_itest_join_environment(... ${MGL_ITEST_COMMON_ENV})` 构造;`add_trace_replay_test` 加 `SPLIT` 后缀并 `-DTRACE_TRANSPORT=`;每条 `DirectGLES.Split.*` 条目带独立 `MOBILEGL_LOG_FILE_PATH`。 -- CI(`.github/workflows/test.yml`):`pipe-gates`(G1–G8 重生成 + diff、生成器 self-test、符号报告门、`MG_Backend`/`MG_State` 禁止 stdio 插桩、dirty-surface、字段归属、R-16 阴性对照 smoke、G5 两族、文档引用 lint);`flatc-check`;`include-graph-check`;`monolith-symbol-report`;`build-linux` / `build-linux-verify` / `build-linux-split`;`integration` / `integration-verify` / `integration-split`(含 `scripts/ci/split_negative_controls.sh` 的 E1/E3(a)/E2 硬门,broad inproc 车道只记录普查);`retrace` / `retrace-verify` / `retrace-split`。`apk.yml` 构建 pull / push / split 三份 APK 并在 AVD 上 retrace。两份 workflow 里的 `feat/disaggregated` 触发器是 **TEMPORARY**,合入 dev 前移除。 +- `MOBILEGL_BUILD_DISAGGREGATED`(默认 OFF)追加 `MG_Remote/**`;OFF 时 `MG_Config::Transport` 是 `constexpr Monolith`。`MOBILEGL_BUILD_DISAGGREGATED_INPROC` 隐含前者并加角色隔离:两个角色靠 apply 线程与控制邮箱分开,而不是给 1494 个 `pGLContext->` 读点加 TLS。 +- `MOBILEGL_TRANSPORT = monolith | inproc | spawn` 是**拓扑**(G1 相关);控制面与数据面由 §11.9 的两个变量选。**split build 不设 `MOBILEGL_TRANSPORT` 时是 monolith 对照臂**。 +- 四个构建 flavour:pull(默认)、push、verify、split;Android 三份 APK flavour(pull / push / split)。 +- 测试接线:ctest `ENVIRONMENT` 用 `mgl_itest_join_environment(...)` 构造;每条 split / spawn / tcp 条目带独立日志路径,日志按角色分文件(`.client.log` / `.server.log`);spawn、tcp 车道与 split 名集合一致(`scripts/ci/spawn_lane_parity.py`),每条目记 arm 证明。 +- CI(`.github/workflows/test.yml`、`apk.yml`):`pipe-gates`(G1–G8、生成器 self-test、符号报告、dirty-surface、字段归属、G5、文档引用 lint)、`flatc-check`、include 闭包、各 flavour 的 build / integration / retrace 车道、APK + AVD。两份 workflow 里的 `feat/disaggregated` 触发器是临时的,合入 `dev` 前移除。 -## 17. P5 / P5b 落地形状:lockstep `inproc`、reply、apply-thread server 与 verb 迁移 +## 17. 阶段落地形状(索引) -### 17.1 verb barrier 与诚实的同地址空间传输 +各阶段落地时的形状、偏差与论证原文在 notes 里;下表只列仍然生效的规则。 -P5 的 `inproc` 是真第二线程,但还是 **lockstep**:每条 class-B verb 发射后 client 的 `EmitAndWait` 等到 `appliedSeq == emitSeq`;`MOBILEGL_IPC_VERB_BARRIER=1` 默认开启。理由不是吞吐,而是 BARRIER-PULLED 字段(G8 表里目前 36 行)尚无记录载体;在这些字段退役前让两线程同时跑,server 会读到 client 的"未来值"(R-1)。barrier 是逐族可退役对象,不是 P6 transport 的要求;它的设备代价(barrier tax)见 `MEASUREMENTS.md` §7.4。 - -同一地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写成 `nullptr`,内容 blob 必须带真实 `SEG_STAGE` offset / 非零 size;decoder 对四种形状分别 `Fatal{ProtocolCorruption}`。`MapPersistent` 在 split 恒 decline;`MOBILEGL_IPC_AUDIT=1` 在 retire 后把 staging 填 `0xDD`,让跨 applier 返回持针的实现下一次读取时可见地失败(R-11:任何 widened read 先过 `RequireStagedCoverageForPendingRanges`,否则 `Fatal{StageSnapshotTooNarrow}`)。apply 角色不得进入 client 的 persistent-map producer(r1)。 - -reply mailbox 以**记录序号作为 slot id**(R-3):slot header 是 `{Seq, Status, Size}`,acceptance 的 Bool 答案与 `MapPersistent` decline 都在既有 verb wait 内读取;`Status=ERROR` 一律 `Fatal{ReplyError}`,未知 status 是 `Fatal{ReplyStatusInvalid}`;读到 reply 之前先由 `appliedSeq` 证明该记录已离开 applier(R-5)。P9 才把这套同步 mailbox 推广成异步池。等待预算:普通 verb / 容量等待 120 s(`Fatal{BarrierTimeout}`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`);`ClientWaitSync` 的 applied/reply 预算 = ceil(timeout ns / 1e6) + 120 000 ms,有限 chunk 避开溢出。这条看门狗只判**死锁**(记录永不退役),不是性能门:lavapipe/llvmpipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,故预算从原 30 s 提到 120 s——monolith 走同一路径本就没有这个上限。 - -### 17.2 71 槽的三类、caps 与 tight readback - -client 表(`Client/EmitTables.cpp`)把后端函数表的 71 个槽分成三类,`static_assert` 钉住 **A = 2**(getter 从 caps mirror 本地回答)/ **B = 54**(发射:P5 的 `Clear`、`DrawArrays`、`ReadPixels`、`Blit`、`Present` + P5b 的 d1 19、i1 7、t2 6、f1 11、`BlitNamedFramebuffer`、sync 5)/ **C = 15**(`Fatal{UnmigratedVerb}` 具名拒绝,永不回落到 monolith applier——R-4)。applier 侧 33 条走生成路由表 + 4 条 escape,`PipeCatalogueTest` 钉 37;生成路由的完备性覆盖值调用与 `&MGPipeApply*` 地址取用(R-17)。 - -能力存活只读 `CapsMirror` 中的 `MGPCaps::CallMask`(R-8);server 的 consumer mask 由 backend 类型显式设置并以实际 op table 校验,**永不**从进程级 `MGPipeGetResourceOps()` 推导。成功的 `MakeEGLCurrent` / `InitCapabilities` 重新发布 snapshot,client 按 generation 采纳(R-12);相同 (dpy, draw, read, ctx) tuple 的重复 make-current 不重发(ID-67)。 - -`ReadPixels` 在线上恒为 **tight**:server 临时设 neutral pack,只向 reply 写 `width*height*bytesPerPixel`;client 用自己持有的 pack state scatter(含 `PACK_SWAP_BYTES` 的 component / packed-word swap,r1)。绑定 PACK PBO 时在 client 侧 `Fatal{UnmigratedVerb, "ReadPixels+PACK_BUFFER"}`;reply 单槽 payload 上限 `2 MiB - 16`,更大 readback 的 carrier 留给 P6+(ID-47/57)。 - -### 17.3 server 角色、shadow 与退出 - -`ServerLoop` 的 `mgl-srv-apply` 是 native context 的终身 owner。`ServerMakeEGLCurrent` 对 tuple **只 bind 一次**,client release 只记账、绝不让 apply thread native-unbind(ID-54)。罕见 EGL 操作经 caller-serialised one-slot control mailbox 进 apply thread;十二个 `Server*` forwarder 是唯一缝。有 backend 却无 thread 时 `Fatal{ApplyThreadNotRunning}`,不允许回落到 app thread。framebuffer death 的驱动调用也经 mailbox 上 apply thread(r1)。 - -`PipeApplier::ApplyOne` 依次 stamp verb、decode/apply、清 stamp;只有 `SessionConsumer::ApplyOne` 每条记录把 `appliedSeq` 加一。`StagedShadowStore` 按 resource twin 复制并合并**精确覆盖范围**;`Ops_H_SubData` / flush / respecify 只把 server-owned copy 交给后端;有传输时 `liveHostBase()` 不得回落到 client 的 `MappedData()`(ID-50/52)。 - -退出顺序:`ShutdownSplitRoles` 先让 client publish 并 bounded-drain,再 shutdown transport / 唤醒 wait,随后 bounded-join apply thread;线程在仍持 context 时 detach decoder、销毁 private backend,最后才销毁 emitter 与 transport。 - -### 17.4 lane 隔离与阶段边界 - -每条 `DirectGLES.Split.*` entry 有独立 `MOBILEGL_LOG_FILE_PATH`;E1/E3 控制从被选 entry 的私有文件取 Fatal,且把"选中条目被跳过"判为失败(ID-53/62)。`integration-split` 是缩减路径的硬门;broad inproc `integration-gpu` 车道只记录普查、不设门(ID-65)。 - -### 17.5 P5b:class-C verb 迁移 - -- **顺序由动态普查决定**(ID-68):79 个 trace × 集成车道在 inproc 下的首阻塞统计(`~/w7/notes/p6/census-classC.md`),所有 Minecraft trace 首阻塞 = `DrawElements` → Minecraft 优先,四包并行(d1 draws、i1 image/compute、t2 XFB/tess、f1 clear/copy/mip)+ 尾包(sync、具名 blit、GLES mip)。 -- **一次迁移 = 一个发射器 + 一个 sink 体**:c0b 先给 25 个已测量槽铺 wire 记录(`Wire/PipeWireCodec` 的行)、`WireVerbSink` 分派与 `ServerVerbSink` 具名 Fatal stub;包只在 `EmitTables.cpp` 把槽从 C 挪到 B、在 `PipeApplier.cpp` 填 sink 体(apply 线程、只从记录与 server 状态取输入)。这些槽到达的是 sink,不是 `MGPipeApply*`。 -- **规则 D**:记录把 GL 调用逐字带在句柄旁边(draw 的 mode/count/type/offset/instance/base 字段、clear 的值类与 drawbuffer 下标、named-framebuffer 的句柄形式),后端保留它的 barrier-pulled 读;未测量的形式**按名拒绝**(`+RENDERBUFFER`、`+UNBOUND` 等)而不是猜。 -- **draw 家族**:19 个索引 / 实例 / multi-draw / indirect 槽全部下沉到 `draw_vbo`;用户索引 span 的 shape/extent 门在 encoder / decoder / sink 三端共用(单 range、宽度 1/2/4、`Uint64(Count) × IndexSize ≤ Size`)。 -- **sync**:`FenceSync` / `ClientWaitSync` / `GetSyncStatus` / `WaitSync` / `DeleteSync` 五条现有 opcode 接线,client 铸造 Fence 句柄,wire 只过 `{slot,gen}`,native fence 归 apply 线程;顺带修掉 `MGL_BACKEND_SLOT_PTR_LOCAL` 对 split 恒返回 nullptr。 -- **具名 blit**:`BlitNamedFramebuffer` 经作用域化 client-shadow read/draw 绑定发布,降为现有 bound backend 调用,退出恢复公开绑定。**GLES mip storage**:前端先定义并发布层级,server 只验证 applier descriptor 的 Levels/extent;registry 身份解析只 Find 不 mint。 -- **P5b 留下的 inproc 依赖**:具名 blit 的 scoped client binding + barrier;mip descriptor 的 barrier-held registry 查询;FBO death 的 inproc mailbox。三者跨地址空间都不成立——它们只是 §17.6 清单里的三行。 - -### 17.6 `inproc` 仍经共享地址空间的访问,与 P5c 的形状(已落地,头 `b88e8487`) - -P5 / P5b 的 wire 只覆盖 verb 记录、`SEG_STAGE` blob、reply 与 caps 快照。对 `a79a0af6` 的只读静态审计(`ROADMAP.md` "P5c 计划",报告 `~/w7/notes/p5c/p5c-audit-v1.md`)列出 59 处仍靠 verb barrier 与同一地址空间才正确的直接访问,最重的四类:**纹理纹素**虽已过 `SEG_STAGE` 但 applier 丢掉指针、Espryt 回读 client 的 `MipmapStorage`(整个纹理家族不在 `FieldOwnership.def`,`rsp` / strict / audit 都看不见);**反向通道**是 apply 线程直接调进 client `MG_State`(`OnBufferWriteback` 传裸指针,`SEG_EVENT` 已铺好但零 producer,十个回调只装了两个);**server 用前端 `GetLifetimeId()` 去 client 的 slot 分配器查找甚至铸造句柄**,而记录里其实已带句柄;**Magma** 直接读 client 的 caps 镜像、直接 `MarkGpuWritten`、直接写 client 的 mip 存储。 - -P5c 的设计决定(六条全部落地,落地形状与偏差以 `MobileGL/MG_Remote/CONTRACT-P5C.md` 为最新权威):(1) **server 端纹理 staged shadow**——`ApplyTextureUpload` 采纳 `SEG_STAGE` 字节,`SyncMipmapsToBackend` 只读它与描述符,`0xDD` audit 因此覆盖纹理;(2) **`SEG_EVENT` 成为唯一反向通道**——`OnBufferWriteback` 的 `MGPBlobRef` 约定 `Seg = kSegEvent`,`OnGpuWritten` / `OnSurfaceChanged` 走同一 ring,Magma 与 Espryt 共用回调,溢出策略按 §11.7;(3) **sink 与 twin 按记录里的句柄解析**(`GetOrCreate(MGPipeHandle)`),server 永不触 `MGPipeSlots()`;(4) **两条控制记录** `applier_reset`(make-current 边)与 `object_death`(framebuffer 首次有 delete opcode),mailbox 只剩 EGL forwarder 给 P6;(5) **值类 BARRIER-PULLED 行改为每 verb 残余值记录或 server 自答**,对象类行保持 barrier 直到 twin 表落地(P3b/P4b、P7、P8);(6) **角色守卫**——split 构建有传输时,apply 线程触前端对象表面、GL 线程触 server 状态表面都是 `Fatal{RoleViolation}`;这是 P5c 的出口门,也是 P6 只做传输替换的前提。 - -落地时的三条结构性补充(契约 §3.1 与 §5.4 的具名豁免):审计漏了三族无句柄站点——绑定记录 P4b 才发射的 ensure/通告族(`MGPipeReverseAnnouncementScope`)、G6 前端键 twin registry(`MGPipeFrontendKeyedRegistryScope`,P3b/P4b 重键)、Magma 拆除期的隐藏资源;两个 scope 内的只读探测是具名、可 grep、带退役阶段的债,不是守卫的洞。 - -### 17.7 P5e:client 在 Espryt draw 路径上跑在 apply 前面(权威为 `MobileGL/MG_Remote/CONTRACT-P5E.md`) - -P5d 把 `inproc` 变成 client 线程 CPU-bound,帧仍是 `client 工作 +(每 draw)等 apply + 交接`。那个等待存在,是因为 draw 的 apply 仍在解引用 client 拥有的内存:`FieldOwnership.def` 的对象类 BARRIER-PULLED 行,以及两个 P5C 具名豁免 scope 里的前端键 twin registry。**P5e 的一句话:client 发布完就走;server 用记录带的句柄解析每一个对象;它唯一可以读的 client 内存,是一条 client 确实被挡在后面的记录的残余 fill。** - -- **barriered 谓词**(§2.1,裁定 3 / ID-83):`MGPipeBarriered(op, payload, applierState)` = 静态 `WaitClass` 列 ≠ `kWaitNone`,或 XFB span 打开时的 `kCtxVerb`,或带 `kDrawClientArrays` 的 draw。两侧算同一个函数、同一份数据——client 在 emit 时用 `ctx.IsTransformFeedbackActive()`,server 用应用过的 `MGPContextValues` 镜像,而 `set_context_values` 在 ring 上先于 verb,所以两者一致。没有第四条升级;加一条是整合者的裁定加契约里的一行。 -- **规则 F**(§0):**unbarriered 记录的 apply 不读任何 client 内存**——不解引用前端对象、不读 BARRIER-PULLED 行、不探测也不铸造 `MGPipeSlots()`、不持有前端对象的 `SharedPtr`。违反是具名 Fatal,**与 `MOBILEGL_IPC_STRICT_ERRORS` 无关**:那个值按构造就是撕裂或陈旧的,没有"计一笔"的臂。barriered 记录原样保留 P5C 的语义(裁定 4 / ID-84),这正是第一次落地能做小的原因:回读、CopyTex、`GetTexImage`、`set_storage_block_binding`、XFB 都留在 barriered 且拖后。 -- **`gPipeInputs` 变成 server 角色内存**(§3):unbarriered verb 的 validate 只跑第 2 步(tracker 走查)和第 3 步(发射器)——它们产出记录,这正是 unbarriered verb 被允许交给 server 的全部——序号自增、verb 戳、撤回 server 戳与整个第 4 步都不跑。`MGPipeInputUnfreshRead` 的计数器在 unbarriered 记录下变成**无条件中止**,这是把 `integration-split-strict` 从"预期红"变成硬绿门的那一处(§7)。 -- **run-ahead 的臂**:`RunAheadArmed()` = `m_barrierArmed && Ipc.RunAhead && Caps().HasCap(kCapRunAheadApply)`,在 `Start` 之后第一次 caps 采纳时**锁存**,此后只能被关掉、不能被打开——已经不等就发布出去的记录收不回来。Magma 的 `CallMask` 永远不带 bit 10(`MGPipeRunAheadCapBitsFor` 是纯函数,`MagmaPipeIdentityTest` 钉住),Espryt 则由 `MG_Backend/Init.cpp` 的 `kMGPipeP5eRunAheadReady` 一个常量把整相位关在惰性里,直到整合提交把它翻过来。 -- **对照臂**:`MOBILEGL_IPC_RUN_AHEAD=0` 是 A/B 的**对照**而不是负控制——server 代码、fill 决定、每一条记录两臂都一样,唯一的差别是 client 等不等;`MOBILEGL_IPC_VERB_BARRIER=0` 保留它自己的意思,并且因为它是那个合取式的第一项,它会直接把 run-ahead 关掉。 +| 原节 | 主题 | 仍生效的规则 | 全文 | +|---|---|---|---| +| 17.1 | verb barrier 与诚实的同地址空间传输(P5) | 同地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写 `nullptr`;reply 以记录序号为 slot id(R-3);等待看门狗 120 s 只判死锁,不是性能门 | [`notes/p5/README.md`](notes/p5/README.md) | +| 17.2 | 后端槽三类、caps、tight readback(P5) | A 本地答 / B 发射 / C 具名拒绝,永不回落 monolith(R-4);能力只读 `CallMask`(R-8);`ReadPixels` 线上恒 tight,client 按自己的 pack state 散射 | 同上 | +| 17.3 | server 角色、shadow 与退出(P5) | apply 线程终身持有 native context,EGL 罕见操作经控制邮箱进 apply 线程;staged shadow 只交 server-owned 拷贝;有界退出顺序 | 同上 | +| 17.4 | lane 隔离(P5) | 每条 split 条目私有日志;E1 / E3 控制把"选中条目被跳过"判为失败 | 同上 | +| 17.5 | class-C verb 迁移(P5b) | 迁移顺序由动态普查决定;一次迁移 = 一个发射器 + 一个 sink 体;记录逐字带 GL 调用参数,未测量的形式按名拒绝(规则 D) | [`notes/p5b/README.md`](notes/p5b/README.md) | +| 17.6 | 共享地址空间访问归零(P5c) | server 端纹理 staged shadow;`SEG_EVENT` 是唯一反向通道;sink / twin 按记录句柄解析;`applier_reset` / `object_death` 控制记录;角色守卫 `Fatal{RoleViolation}` | [`notes/p5c/README.md`](notes/p5c/README.md) | +| 17.7 | run-ahead(P5e) | 规则 F:unbarriered 记录的 apply 不读任何 client 内存(违反即具名 Fatal,与 strict 开关无关);`gPipeInputs` 是 server 角色内存;`RunAheadArmed()` 在首次 caps 采纳时锁存,只能关不能开;`RUN_AHEAD=0` 是 A/B 对照,`VERB_BARRIER=0` 会连带关掉 run-ahead | [`notes/p5e/README.md`](notes/p5e/README.md) | ## 附 A:开关 -CMake: - -| 选项 | 默认 | 状态 | -|---|---|---| -| `MOBILEGL_BUILD_DISAGGREGATED` | OFF | 已落地 | -| `MOBILEGL_BUILD_DISAGGREGATED_INPROC` | OFF | 已落地(P5,隐含前者) | -| `MOBILEGL_BUILD_SERVER_SPIKE` | OFF(仅 Android) | 已落地(spike A,非出货) | -| `MOBILEGL_PIPE_PUSH` | OFF | 已落地(P1;push / verify / split flavour 都打开它) | -| `MOBILEGL_PIPE_VERIFY` | OFF | 已落地(P1;隐含 `PIPE_PUSH`,编译 `SnapshotFromGLContext()` 与 G4 比对器,永不出货,P13 后保留) | -| `MOBILEGL_PIPE_LEGACY_MEMOS` | ON | 已落地(P2;OFF 时不编译 pre-handle 臂;`PIPE_PUSH=OFF` 强制回 ON) | -| `MOBILEGL_FLATC_EXECUTABLE` | 空 | 只服务 `flatc-check` | -| `MOBILEGL_BAKED_INTERNAL_SHADERS` | ON | 计划(P7) | +CMake:`MOBILEGL_BUILD_DISAGGREGATED`(OFF)、`MOBILEGL_BUILD_DISAGGREGATED_INPROC`(OFF,隐含前者)、`MOBILEGL_PIPE_PUSH`(OFF;push / verify / split flavour 打开)、`MOBILEGL_PIPE_VERIFY`(OFF;隐含 push,永不出货)、`MOBILEGL_PIPE_LEGACY_MEMOS`(ON)、`MOBILEGL_BUILD_SERVER_SPIKE`(仅 Android spike)、`MOBILEGL_FLATC_EXECUTABLE`(只服务 `flatc-check`)。 运行时,MGPipe(`MobileGL/Config.h`、`ConfigLoader.cpp`): | 变量 | 默认 | 说明 | |---|---|---| -| `MOBILEGL_PIPE_PUSH` | pull 构建 `0`;push 构建 `0x1fff` | 子系统位图(`MG_Pipe/MGPipe.h`,位永不复用):`0x01` 渲染状态、`0x02` pixel pack、`0x04` patch、`0x08` attrib defaults、`0x10` residual、`0x20` Espryt slots、`0x40` Magma vertex input(以上 P2 = `0x7f`)、`0x80` resources、`0x100` vertex input(P3a = `0x1ff`)、`0x200` framebuffer、`0x400` 纹理资源、`0x800` sampler、`0x1000` program(P4a = `0x1fff`)。依赖(两侧都拒,客户端不发射):位 8 要 7;位 11 要 10;位 9 要 10;位 10 要 7 和 11。位 63 `kMGPipeBehaviourNoCsoContentAddressing` 是行为对照不是子系统。`0` = 全 pull,只在 `LEGACY_MEMOS` 编进了 pre-handle 臂时才是有效对照 | -| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 逐 draw 逐字段影子比对;首个分歧是否 abort;篡改字段 / 抽掉填充戳记两个阴性对照(verify 构建才有) | -| `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` | 0 | 阴性对照 C:故意打掉句柄身份让 `HandleRecycle` 的 ABA 臂重现旧污染 | -| `MOBILEGL_PIPE_STATS` / `_STATS_PERIOD` / `_STATS_FILE` | 0 / 120 / 空 | 边界计数器(附 B);每多少帧一条汇总行;teardown JSON(trace app 从不到达 teardown,设备上只有周期行) | -| `MOBILEGL_PIPE_LEGACY_MEMOS` | ON | 三态读取,只有显式 falsy 才关 | -| `MOBILEGL_PIPE_TEXEL_RETAIN_MB` | 0(0–4096) | 纹理拉取保留 LRU | -| `MOBILEGL_PIPE_INDEX_MIRROR_MB` | 64(0–4096) | 索引宿主镜像预算(P8) | +| `MOBILEGL_PIPE_PUSH` | pull `0`;push `0x1fff` | 子系统位图(`MG_Pipe/MGPipe.h`,位永不复用;依赖两侧都拒);位 63 是"关 CSO 内容寻址"的行为对照 | +| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 影子比对与两个阴性对照(verify 构建才有) | +| `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` | 0 | 故意打掉句柄身份的阴性对照 | +| `MOBILEGL_PIPE_STATS` / `_STATS_PERIOD` / `_STATS_FILE` | 0 / 120 / 空 | 边界计数器(附 B);dump 按角色写 `.client.json` / `.server.json` | +| `MOBILEGL_PIPE_LEGACY_MEMOS` / `_TEXEL_RETAIN_MB` / `_INDEX_MIRROR_MB` | ON / 0 / 64 | pre-handle 臂、纹理拉取保留、索引镜像预算(P8) | 运行时,传输与 IPC: | 变量 | 默认 | 说明 | |---|---|---| -| `MOBILEGL_TRANSPORT` | `monolith` | `inproc` 已落地;`spawn` / `unix:` / `pipe:` 是 P6,当前具名拒绝并回落 monolith | -| `MOBILEGL_IPC_RING_MB` | 8 | `SEG_CMD`;单条记录至多一半 | -| `MOBILEGL_IPC_STAGE_MB` | 32 | `SEG_STAGE`;目标负载 profile 显式 256 | -| `MOBILEGL_IPC_SPIN_US` | 50 | park 前自旋;P5d 三轮起自旋按一次性校准的迭代预算走、稳态不读时钟,`0` = 不自旋直接 park | -| `MOBILEGL_IPC_EVENT_WAIT_MS` | 2000(1–600000) | PH-6(ID-P7-2),只由 server 读(`ServerSpawn.cpp` 放行给 spawn 子进程):run-ahead 下一条反向事件遇到满的 `SEG_EVENT` 时,server 等 client 腾出空间的**整笔预算**(不是每次 park 的)。到期(client 不排空 / 只涓流不腾够)→ `ReverseChannelForfeit{NotDraining\|TooSlow}`;client 已走(bell 已死——shm 上靠 server bell 挂在控制套接字上的死亡见证——或控制流 EOF)→ `{PeerGone}`、会话被 Stop(如控制流收到畸形帧)→ `{Stopped}`,这两种立即结束等待,所以旋钮可以大于 `ServerLoop::Stop()` 的 5000 ms join。之后该事件及其后全部计数丢弃(`eventDropped`),apply 循环按正常 Stop 退出,控制循环每一轮都查 apply 线程是否还在,会话 exit 0,supervisor 照常欢迎下一连接。取代原先 30000 ms × 两轮后的 `Fatal{EventRingOverflow}`。lockstep 臂(无 `kCapRunAheadApply`,即 Magma)不等待,仍是 P5C 的 Fatal | -| `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` | 64 | persistent-map 推送块粒度;`0` 是 E3(a) 阴性对照 | -| `MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS` | 1 | 推送只发内容变了的块(追踪 buffer 走 mprotect 位图,未追踪走内容哈希);`0` 恢复全范围推送(A/B 对照) | -| `MOBILEGL_IPC_BATCH_WAITS` | 1 | 值类记录(无 reply slot 的 kCtxState/kCtxCso/kCtxObject)发布即返回,barrier 推迟到下一个拉取类 verb;**`generate_mipmap` 例外**(它的 apply 读 `MGB_CTX->GetActiveTextureUnit()`,规则:只有 apply 不读残余填充字段的记录才可免等);`0` 恢复 R-1 逐条 barrier(`MOBILEGL_PIPE_VERIFY` 强制 0) | -| `MOBILEGL_IPC_ADOPT_TIER` | 2 | `auto/0/1/2`;P5 split 用 emulated(T2) | -| `MOBILEGL_IPC_VERB_BARRIER` | 1 | 每 verb 等 `appliedSeq == emitSeq`;`0` 只作 E1 阴性对照,并且因为它是 `RunAheadArmed()` 合取式的第一项,它同时把 run-ahead 关掉 | -| `MOBILEGL_IPC_RUN_AHEAD` | 1 | P5e:发布完 unbarriered 记录即返回(§11.6 的 `WaitClass` 列)。**只是合取式的一半**——server 不发布 `kCapRunAheadApply` 时它等于 0 并日志一次;`0` 是 **A/B 对照**(两臂 server 代码与记录完全相同,只差 client 等不等),不是阴性对照。`MOBILEGL_PIPE_VERIFY` 强制 0 | -| `MOBILEGL_IPC_PRESENT_CREDIT` | 1 | P5e:client 可以在手的 present 数,range 1..8。credit 1 = 一帧重叠、最多一帧附加延迟;2 是设备测量臂。stats 行的 `credit-waits` 是它真正阻塞的次数 | -| `MOBILEGL_IPC_STRICT_ERRORS` | 0 | BARRIER-PULLED residual input 提升为具名 Fatal | -| `MOBILEGL_IPC_AUDIT` | 0 | retire 后 `0xDD` 填退休 staging | -| `MOBILEGL_IPC_SERVER_AFFINITY` | `auto` | apply 线程亲和性;日志报告内核实际采纳的掩码(Redmi 的内核对 app 线程一律忽略 `sched_setaffinity`,解析为 0xff) | -| `MOBILEGL_IPC_SERVER_PATH` | 空 | P6 消费 | +| `MOBILEGL_TRANSPORT` | `monolith` | 拓扑:`monolith` / `inproc` / `spawn` | +| `MOBILEGL_IPC_CONTROL` / `MOBILEGL_IPC_DATA` | `fork` / `auto` | 控制面端点与数据面(§11.9);`MOBILEGL_IPC_SERVER_PATH` 定位 fork 出的 server | +| `MOBILEGL_IPC_TOKEN` / `MOBILEGL_IPC_REQUIRE_SAME_BUILD` | 空 / 0 | 令牌(≥16 字节;无令牌只允许 loopback);要求两端同一构建(车道纪律) | +| `MOBILEGL_IPC_PREAUTH_*` / `MOBILEGL_IPC_AUTH_BACKOFF_*` | — | server 预认证的上限与失败退避(Ph) | +| `MOBILEGL_IPC_CONTROL_TIMEOUT_MS` / `MOBILEGL_IPC_COLD_START_MS` | 5000 / 20000 | 控制回复的稳态上限与冷启动预算 | +| `MOBILEGL_IPC_LOG_FORWARD` | 远端时开 | server 日志经控制连接前送,client 写进自己的 `.server.log` | +| `MOBILEGL_IPC_SURFACE` | `offscreen` | `server` = 使用 server 自有窗口,client 无头(P12) | +| `MOBILEGL_IPC_RING_MB` / `MOBILEGL_IPC_STAGE_MB` | 8 / 32 | `SEG_CMD` / `SEG_STAGE`;stage 的 1/4 是内容分块预算 | +| `MOBILEGL_IPC_RUN_AHEAD` | 1 | P5e run-ahead;只是合取式的一半(server 须发布 `kCapRunAheadApply`);`0` 是 A/B 对照 | +| `MOBILEGL_IPC_VERB_BARRIER` | 1 | `0` 只作阴性对照,且同时关掉 run-ahead | +| `MOBILEGL_IPC_PRESENT_CREDIT` | 1 | 1..8,§14 | +| `MOBILEGL_IPC_BATCH_WAITS` | 1 | lockstep 下值类记录发布即返回;verify 强制 0 | +| `MOBILEGL_IPC_EVENT_WAIT_MS` | 2000 | 反向事件遇满环时 server 等 client 排空的整笔预算(§11.7) | +| `MOBILEGL_IPC_WIRE_DEFERRED_MB` | 64 | Magma wire 臂延迟回收的水位线;`0` 是 M2 阴性对照 | +| `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` / `_PERSISTENT_HASH_SUPPRESS` | 64 / 1 | persistent-map 推送粒度 / 只推变化的块;`0` 是对照 | +| `MOBILEGL_IPC_ADOPT_TIER` | 2 | `auto/0/1/2`;T0 / T1 今天是 Fatal-at-use | +| `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` / `MOBILEGL_IPC_ROLE_SPLIT_STATE` | 0 / 0 / 0 | 残余输入读升级为 Fatal / 退休 staging 填 `0xDD` / 双块演练(P5f) | +| `MOBILEGL_IPC_SPIN_US` / `MOBILEGL_IPC_SERVER_AFFINITY` | 50 / `auto` | park 前自旋预算 / apply 线程亲和(Redmi 内核忽略) | +| `MOBILEGL_IPC_RESPAWN` | — | 具名拒绝:没有阶段实现 server 重启后的全量重推 | -P6+ 生效:`MOBILEGL_IPC_POLL_ESCALATE`、shadow shm、`MOBILEGL_IPC_RESPAWN`、`MOBILEGL_IPC_IDLE_EXIT_S`(`MOBILEGL_IPC_PRESENT_CREDIT` 已在 P5e 生效,见上表)。显式不设立:`MOBILEGL_IPC_PROGRAM`(没有 relink 档)、`MOBILEGL_IPC_VALIDATE_SERVER`(server 没有 `MG_Impl` 校验器)。 +显式不设立:`MOBILEGL_IPC_PROGRAM`(没有 relink 档)、`MOBILEGL_IPC_VALIDATE_SERVER`。计划中未接线:`MOBILEGL_IPC_POLL_ESCALATE`(P10)、`MOBILEGL_IPC_IDLE_EXIT_S`(未解析)。 ## 附 B:边界计数器(`MobileGL/MG_Util/Metrics/PipeStats.h`) -关闭时每站点一次全局 load + 一条永不命中的分支。字节类:`stage-buffer`、`stage-texture`、`stage-ubo-global`、`stage-ubo-named`(只有 Magma 贡献,D-B8)、`stage-vertex-client`、`stage-index-client`、`stage-indirect-cmd`、`persistent-map-push`(`pmap`)、`residual-value-block`(`resid`)、`cso-blob-bytes`(`csob-blob`)。调用类:`draws`、`accessor-calls`(约 10 个热入口的**静态**计数,是下界——把读点搬走与把工作去掉在它上面长得一样,判性能只看 CPU 时间序列与 memo 门)、`texture-upload-emissions/box/rect/jobs`、`csom`/`csob`(CSO 铸造 / 绑定)、`mpr`、`trp`(纹理重铸拉取)、`rsp`(residual reads,只是有 stamp 读点的下界)、`maxrec`/`ringwraps`/`ringpads`/`ringwaits`(run totals)。六个 memo 门(`ers`/`etl`/`eub`、`mfp`/`mpm`/`mdt`)各计 hit/miss。每 `MOBILEGL_PIPE_STATS_PERIOD` 帧一条 `MGPipe stats:` 汇总行;站点清单——哪些路径**没有**接线——写在 `PipeStats.cpp` 头部,那份清单是契约。逐 dirty 位触发率与每 draw payload 直方图已实现但从未接进汇总行(未测)。 +关闭时每站点一次全局 load + 一条永不命中的分支。字节类(stage buffer / texture / UBO / client 数组、`pmap`、`resid`、`csob-blob`、`seg` = 写进 `SEG_STAGE` 的字节)、调用类(`draws`、`accessor-calls`、纹理上传 emit / box / rect / jobs、`csom` / `csob`、`mpr`、`trp`、`rsp`、`wrec`、`maxrec` / `ringwraps` / `ringpads` / `ringwaits`、门铃 `srv` / `srvpark` / `cli` / `clipark`)与六个 memo 门的 hit/miss。每 `MOBILEGL_PIPE_STATS_PERIOD` 帧一条 `MGPipe stats:` 汇总行,按角色写日志。读法陷阱:`accessor-calls` 是静态下界,判性能只看 CPU 时间;spawn 下 client 不推进帧窗口(`frames=0`),窗口化字段在 spawn client 上无效,run total 仍有效([`notes/p6/README.md`](notes/p6/README.md) §12.1)。站点清单(哪些路径**没有**接线)写在 `PipeStats.cpp` 头部,那份清单是契约。 diff --git a/docs/Disaggregated/CURRENT_STAGE_PROGRESS.md b/docs/Disaggregated/CURRENT_STAGE_PROGRESS.md index 9c6d795c..16aae554 100644 --- a/docs/Disaggregated/CURRENT_STAGE_PROGRESS.md +++ b/docs/Disaggregated/CURRENT_STAGE_PROGRESS.md @@ -1,262 +1,51 @@ -# 当前阶段进度 — P6.5:全 TCP 传输 +# 当前阶段进展 — P12「server 自有屏幕窗口」子集 -## P6.5 第一波(2026-09-22) +> 只记**当前阶段**的摘要:在哪、落地了什么、门到哪一步、下一步、阻塞。细节与证据都在 [`notes/p12/`](notes/p12/README.md)(计划与交接 [`PLAN-P12.md`](notes/p12/PLAN-P12.md),裁定 [`INTEGRATOR-DECISIONS-P12.md`](notes/p12/INTEGRATOR-DECISIONS-P12.md))。已收官阶段见 [`ROADMAP.md`](ROADMAP.md) 阶段表。随每次合并 / 真机窗口更新。 -实现、代码审查与主要正确性门已完成;**已落地并推送 `feat/disaggregated@fe28bdb5`(2026-09-22)**(原 -在 `codex/p65-all-tcp` 的 WIP + 本次审查修复 + 收尾文档一并合入)。完整 39 例 device golden 矩阵与全套 -链路必测数按用户「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**。 +**更新:2026-09-24** · 分支 `feat/disaggregated`(P12 子集与审查轮已并入)· 上一阶段 P7 已于 2026-09-23 收官(ID-P7-63) -- TCP 控制、ShmLink/StreamLink 数据面、布局指纹、portable program archive v2、令牌/Refuse、 - 设备 supervisor/前台服务、日志前送与有序关闭均已接线。 -- 初始 caps 到达前误锁为 lockstep 的启动竞态已修复。READY7 主机 TCP **102/102**、 - Redmi TCP **102/102**,零 skip;两臂逐例确认 **run-ahead ARMED**,无 fallback/demotion。 - 保留车道 spawn **102/102**、inproc **186/186**,零 skip。 -- **代码审查(四并行切片:数据面 StreamLink / wire 定宽 / 控制面握手)无硬正确性缺陷**;两处修复已落地—— - `SocketTransport::AcceptPair` 重试 `EINTR`/`ECONNABORTED`(Medium,防常驻 server 因 accept 瞬态 `return 73` 整体退出)、 - `mobilegl_server_main` 自设 `MOBILEGL_IPC_ROLE=server`(Low,不再盲信 launcher);两处均在 `MG_Remote/` - (`MOBILEGL_BUILD_DISAGGREGATED` 门内),G1 monolith `.text` 构造性不受影响。其余为跟进项(`MobileGLServerService.onDestroy` - 子进程泄漏→P12、`gen_pipe.py` 裸 `char` 符号性普查无实活风险、测试缺口等),见 - [`code-review-findings.md`](notes/p65/code-review-findings.md)。 -- **修后固定制品再验收**(WSL client 与设备 APK 同 build stamp `p65-acceptfix-20260922`,`REQUIRE_SAME_BUILD=1` 真实通过): - 主机 full unit **2399/2399**、P6.5 子集 **147/147**、integration-tcp loopback **102/102**、 - **device-102 102/102,全 102 条 client 日志 `run-ahead ARMED`**、代表性 device retrace - OpenRA SSIM **1.0** / startup **0.999999511** / in-world **0.999995438**,三条全 **ARMED**。 -- G1 符号 **0/0/0/0**,`.text` 逐字一致;真实远端 kill **133 ms**、真实 Wi-Fi 中断 **5053 ms** 闩住 device loss。 -- 原有单角色日志读取的 CI 缺陷已与上游修复合并;模拟器掉线分类另有回归验证。 -- **残余(CI/后续)**:正式 retrace 的完整 **39 项** split 子集 device 矩阵(DirectGLES 现有阈值、OpenRA 1.0; - `ci:false` 的 rd12 单列)与链路必测数(逐帧 `kWaitReply`/RTT、`SEG_STAGE` 字节/帧、`PRESENT_CREDIT` 1/2/3、 - loopback tcp-vs-spawn 逐线程 CPU)。`minecraft-1.21.11-main-menu` 的 Redmi 32B UBO 对齐 golden 伪差为 - ci-false(设备实图 SSIM 0.998729),不作本波红。跑器 `tools/trace_replay/run_tcp_matrix.py`(支持进度超时、 - 进程组清理、实际 run-ahead 证明与可核验 checkpoint)已入库。 +## 1. 一句话 -契约见 [`CONTRACT-P65.md`](../../MobileGL/MG_Remote/CONTRACT-P65.md), -制品身份与证据总索引见 [`evidence-index.md`](notes/p65/evidence-index.md); -具体结果与未决项见 [`validation-status.md`](notes/p65/validation-status.md)、 -[`wire-and-validation.md`](notes/p65/wire-and-validation.md)、 -[`performance.md`](notes/p65/performance.md)。以下保留 P6 收官时的原始进度快照。 +Android 上的 server 自建窗口(自己的 SurfaceView)把 IPC 渲染流**上屏**,client 以 `WindowKind::ServerOwned` **完全无头**接入。子集已实现并入,**未收官**:审查轮修复之后欠一次真机复测,两个出口门都还没打。 -## P6 收官快照 +## 2. 已落地 -> **这份文档只记当前阶段(P6)。** 已收官阶段(P0…P5f)的逐门数字与证据在 -> [`ROADMAP.md`](ROADMAP.md) 的阶段表与各 `P5*` 报告里,不在这里重复。规范契约见 -> [`MobileGL/MG_Remote/CONTRACT-P6.md`](../../MobileGL/MG_Remote/CONTRACT-P6.md);设计见 -> [`ARCHITECTURE.md`](ARCHITECTURE.md)。 - -**状态(2026-09-22):P6 已收官。** 全部包(a6…t6)落地,出口门第 1–8 项全部达成:门 7(真机 -`2f7cbe2e` reboot-clean、同热窗口配对 A/B)判读为 **tie**——`inproc` 与 `spawn` 的 client CPU/帧 -差 −0.15%(会话 1)/ +0.16%(会话 2),都落在臂内散布之内,如实记录、不设门;门 8 的三个强制 -性能数(socket 门铃 vs condvar、`SEG_STAGE` 字节/帧、chunking 后记录/帧)已采齐,逐数见 §2; -G1 pull 构建 **0/0/0/0** 且 `.text` 逐字节相同。测量期的源头:门 7 报告记 `75dd0cb1`、门 8 的 -wire 计数器与真机第二轮记 `94c130d6`。分支 `feat/disaggregated` 其后又落地两个 P6.5 设计提交 -(`872370ba`、`4e7bf0fe`),**现头 `4e7bf0fe`,与 `origin` 同步**;**工作树另有未提交的 P6 收尾 -改动**(wire 计数器、`ServerMain` 的 `PipeStats` 武装、`MOBILEGL_PIPE_STATS_FILE` 角色派生、设备行 -与工具入库、测试),见 §1.1。三种传输形态(`monolith` / `inproc` / `spawn`)在桌面(WSL lavapipe) -与真机(Redmi `2f7cbe2e`)上都跑通了真实 trace 的两进程 retrace,逐像素对上 golden。性能**只记录、 -不设门**。 - ---- - -## 1. 包状态 - -契约把 P6 切成 a6(审计)→ c6(契约)→ lk / so / sm / cp / hs / dl / st / t6。全部已落地: - -| 包 | 落地什么 | 状态 / 证据 | +| 面 | 内容 | 出处 | |---|---|---| -| **a6** | 进程装配与链接边界的核验;198 行审计;184 符号的链接实验(server 不能不链 `MG_Impl`) | ✅ `c25a7760`;[`notes/p6/a6-audit-v1.md`](notes/p6/a6-audit-v1.md)、[`a6-link-experiment.md`](notes/p6/a6-link-experiment.md) | -| **c6** | 规范契约(21 条裁定)+ 数据面缝 `Transport/ILink.h` / `StreamLink.h`(声明不接线) | ✅ `b6293957`;`CONTRACT-P6.md` | -| **lk** | `RingControl` 按写者重组 + 可证伪的 per-field 断言;数据面缝的 pre-flight | ✅ `99649a90`、`db62d674` | -| **so** | `SocketTransport`(AF_UNIX SOCK_STREAM + SCM_RIGHTS),12 条 ITransport 契约用例 | ✅ `5f846d96` | -| **sm** | 两个**独立启动**的进程(非 fork),靠 name 汇合、共享匿名段;D1c 的六谓词里 D10 需要的三个,四个「编进来却永不武装」的守卫接线 | ✅ `70c45ab9`、`11807ac7`、`e3bb6d8b` | -| **cp** | EGL 控制帧跨进程并应答;`InitCapabilities` 上线(wire kind 11);真实 trace 两进程 retrace 逐像素通过 | ✅ `5239ae0c`、`7e244b37` | -| **hs** | build stamp 从「静默空串」变成显式 present 标志入指纹;客户端补检 `abiMajor/Minor`;`Hello::pid` / `Welcome::serverPid` 携带真实 pid | ✅ `90f9a790` | -| **dl** | device-lost 闩(取自描述符的挂断,绝不取自超时);D5b「alive-but-silent」具名诊断;`MOBILEGL_IPC_RESPAWN` 具名拒绝;`Session::Fail` 漏斗(`FatalFamilies.def` 词表 + 90 站点归一 + `SessionFault` 帧向对端命名家族);Fatal 普查门 | ✅ `557dc2c1`、`8b13d8a8`、`3399b879`、`3a12a48a`、`4bb4d4fa`、`b46da700`、`77cbd176` | -| **st** | fb-slot memo 的具名 Fatal(`nullptr==nullptr` 读成缓存命中、返回未填槽的潜在崩溃,sm 关掉可达性、st 出声) | ✅ `e3bb6d8b` | -| **t6** | `integration-spawn` 车道(102/102);集合一致做成结构性(一个宏两条臂)+ 校验门;per-role 日志 | ✅ `15f5b54b`、`5fb54f03` | +| wire | `WindowKind::ServerOwned`、控制修订 2 → 3、`SurfaceReply` 带回窗口真实尺寸或具名拒绝 | PLAN-P12 §1 | +| server 窗口 | `ServerDisplay`(钩子 + 租约 + 有界 3 s `Detach`);会话表面模式首次创建即闩定(`SurfaceModeMismatch`);无显示具名拒绝(`NoServerDisplay`);失窗 → `Fatal{ServerWindowLost}` → client 读干净 device-lost | ID-P12-2、ID-P12-3 | +| client | `MOBILEGL_IPC_SURFACE=server` → 无头窗口 surface,不发 `SetWindowHandle`;resize 变成对 server 窗口的几何请求,采用 server 回的真实尺寸 | ID-P12-10 | +| 进程内 server | `MobileGLDisplayActivity`(`:mglwin`)里一个线程跑 TCP server:会话串行、会话间重置闩、后端按进程钉住;与离屏 `MobileGLServerService` 互斥,一台设备同一时刻一个 server | ID-P12-4 | +| 审查轮 | 10 个问题已修;两个大项:批内失窗检查(`DrainRing` 每次 pop 前)、会话间 Espryt 单元影子悬空;另有推流中停机、会话结束清理、陈旧尺寸、日志转发 | ID-P12-5 … 11 | -配套:CI 的 `retrace-split` 从「1 case × 1 后端 × inproc」扩成「全 CI 矩阵 × 两后端 × inproc/spawn」, -`split` 键改成 opt-out;Android CI 新增 `spawn-acceptance` 臂,真机 App 从 `nativeLibraryDir` 解析 -server 路径(`dc5bd935`、`d83fc664`、`53406749`、`7c8120cc`)。 +## 3. 门 -### 1.1 收尾(2026-09-22) - -下列改动**在两轮测量(门 7 的 `75dd0cb1` 头、门 8 的 `94c130d6` 头)之后落地、目前仍在工作树里未 -提交**(其后两个提交 `872370ba`、`4e7bf0fe` 只改文档与 `Transport/StreamLink.h` 的注释,不含这些代码): - -- **wire 计数器**:`PipeStats` 新增 `ByteClass::StageSegmentBytes`(唯一收口点 - `PipeWireCodec.cpp:878` `StageAllocate`)与 `CallClass::WireRecords`(`:1188` `EncodeRecord` 提交 - 路径),外加一个 staged-blob 直方图(复用 `PayloadBucketOf` 的桶边界,只经 JSON dump 出口); - 汇总行新增 `seg=` / `wrec=` / `wrec/f=`(后者与 `draws/f=` 同形,无 Present 时按既有规则退回窗口总量)。 - 两个计数器都在 `#if MOBILEGL_PIPE_PUSH` 内,落点选在唯一收口点而不是调用方,否则等于维护一张 - "今天存在哪些 producer" 的名单。 -- **`ServerMain` 的 `PipeStats` 武装**:spawn server 此前从不武装计数器(红一次的证据: - **修复前 spawn server 0 条汇总行、修复后 29 条**,同一条 spawn trace、两侧 SSIM 均 1.000000)。 - `Init()` 放在 `MG_ConfigLoader::Init()` 与 `MGPipeSetServerProcessRole(true)` **之后**、 - `Shutdown()` 放在 `loop.Stop()` 之后 `_exit(0)` 之前——两处顺序都是承重的(前者让 latch 读到真 - 配置值,后者让 apply 线程不再发布时取运行总量与 JSON dump)。见 §2.1。 -- **`MOBILEGL_PIPE_STATS_FILE` 角色派生**:与日志 sink 同一套命名规则(库 `RoleLogPath` 导出、 - PipeStats 调用而非复制),dump 与 banner 都印/写 `.client.json` / `.server.json`, - **基名不再指向任何文件**——没跟上改名的读取方会当场 `ENOENT`,这是刻意的。这一条在 G1 上红了 - 三次才做对(无条件派生 → `.text +24`;加 `#if` 但两臂共用局部 → 1 symbol resized;两臂分离但 - 重写 `MGLOG_W` 格式串 → `.rodata +64`),最终形态是 pull 臂的代码逐语句原样、所有新增都在 `#if` 内。 -- **`pin_device.sh` 新增 `2f7cbe2e` 条目**:本板 GPU 钳 **1050 MHz**(`thermal_pwrlevel` 写 0 读回 1、 - `max_gpuclk` 只读 1 050 000 000、devfreq `max_freq` 同值),因此**绝对值与 1100 MHz 时代的 - `35d0befa` 行不可比**,只有同会话的配对可比。三种动作(`check` / `pin` / `unpin`)均已实测。 -- **工具进树**:`tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}`(真机配对 A/B 会话 - 运行器 + 只读渲染器,门 7 快照里的 `gate7_ab.py` / `render_gate7.py` 是它们的前身)与 - `tools/trace_replay/run_android_retrace_local.py`(+19 行):`MOBILEGL_TRACE_PACKAGE` 覆盖, - 让开发包能**装在既有 trace 包旁边**而不是替换它(两个不同密钥签的 APK 不能共用一个 id; - 缺省即原行为)。四个文件目前都在工作树里**未提交**。 -- **测试**:`PipeStats` **25/25**、全 unit **2367/2367**、`integration-spawn` **102/102**; - G1 复验 **0/0/0/0** 且 `.text` **逐字节相同**(pull 配置下两侧各 10 158 850 字节,`cmp` 无差异)。 - 逐条 red-once 与 G1 三次泄漏见 - [`notes/p6/gate8-wire-counters.md`](notes/p6/gate8-wire-counters.md) §9.7.3。 - ---- - -## 2. 出口门(契约 §9) - -| # | 门 | 状态 | +| 门 | 状态 | 证据 | |---|---|---| -| 1 | **G1** pull 构建 0/0/0/0、`.text` 不变 | ✅ 符号 0 差异、`.text` 恒为 `0xa52203`(每次改动后复测) | -| 2 | **G2/G14** `integration-spawn` 名集合 == `integration-split` | ✅ `scripts/ci/spawn_lane_parity.py`:可比 102 == 102;名只增不删 | -| 3 | `integration-spawn` 与 split 同数绿 | ✅ 102/102 | -| 4 | 进程树:运行时恰多一个子进程、结束后为零 | ✅ `ServerSpawnTest.StartsAServerProcessAndHandshakesAcrossIt`(`CountOwnChildren`) | -| 5 | Arm 证明(ID-124):每条 spawn 条目记子进程 pid 与 `transport=spawn` | ✅ `--require-spawn` 门要求 ConfigLoader marker **且** `spawn ARMED - pid N` | -| 6 | 每包 red-once(R-16) | ✅ dl 的 S2/S7、hs 的 serverPid、sm/st 的守卫塌缩、t6 的名集合漂移均已跑红一次 | -| 7 | **设备**:Redmi reboot-clean、同热窗口配对 A/B | ✅ **配对 A/B 判读 tie**:会话 1(boot id `e69d0329…`→`107f24d3…`)六臂交错 `monolith,inproc,spawn,inproc,monolith,spawn` × best-of-3 —— 18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal{`;client CPU p50 ms/帧 **inproc 7.880/7.889、spawn 7.876/7.869**(均值 −0.15%,符号随顺序翻转,臂内散布 0.2–4.2%),`monolith` 约贵 30%。会话 2(boot id `107f24d3…`→`7e6c6e9f…`)inproc/spawn 交错 × best-of-2:4/4 OK、8/8 `PINNED`、0 `Fatal`;client CPU **7.987/7.989 vs 7.992/8.009**(+0.16%,亦在 0.14–0.69% 臂内散布内)。差值在臂内噪声内,**如实记录、不设门**。见 §3、[`notes/p6/gate7-device-ab.md`](notes/p6/gate7-device-ab.md)、[`notes/p6/gate8-doorbell-device.md`](notes/p6/gate8-doorbell-device.md) | -| 8 | **性能只记录**,但三个数强制:socket 门铃 vs condvar、`SEG_STAGE` 字节/帧、chunking 后记录/帧 | ✅ 三个数齐(见 §3.2 / §3.3 与 [`notes/p6/gate8-doorbell-device.md`](notes/p6/gate8-doorbell-device.md)、[`notes/p6/gate8-wire-counters.md`](notes/p6/gate8-wire-counters.md)):① socket 门铃 vs inproc condvar —— client **121.1 waits/帧**、5.6–6.1 parks/帧,两传输逐字相同(run total 30 401–30 407);server inproc **10 866–11 963** waits/帧、5.0–6.2 parks/帧(park 率 0.042–0.06%),spawn **10 415–10 416** waits/帧、4.66–4.90 parks/帧。判读(诚实):socket 阻塞读**既不更便宜也不更贵**,契约 §9「阻塞读可能更便宜」**未兑现**;spawn server waits 少 8.9–12.9% 归因于 handoff 纪律而非门铃本身;唯一 socket 略差的列是 clipark/f(6.07 vs 5.60/5.75,每帧多约 0.4 次 park)。② `SEG_STAGE` 字节/帧(inproc,真实 trace retrace):OpenRA **130.8 KB median / 8.01 MB steady mean**(含启动负载 8.07 MB),rd12 **816.2 KB median / 1.53 MB steady mean**(含负载 10.79 MB)。③ 分块后记录/帧:OpenRA **211 median / 206 mean**,rd12 **7 497 median / 7 504 mean**;`maxrec=1808` 与分块前相同,默认 8 MiB chunk 预算在这两条负载上**从未被打到** | +| G1 | ✅ pull 构建同机前后 `.text` 一致、符号 0 增 0 减 | ID-P12-15 | +| 主机门 | ✅ unit 2605/2605,spawn / tcp 与三条 Magma 双进程车道全绿;剩余红全部归容器的 CMake/CTest 版本 | ID-P12-14 | +| 真机七项检查(Redmi `2f7cbe2e`) | ✅ 审查轮之前(`29b7b284`):双后端上屏 ssim 1.0、串行 + `Refuse{Busy}`、失窗 device-lost、离屏不回归、无显示具名拒绝、一台设备一个 server;⏳ **审查轮之后未复测** | PLAN-P12 §2、ID-P12-13 | +| 出口门 (a) | ❌ FCL 同机 spawn、双后端入世界、**杀 server** 产生干净 device-lost(本轮的驱动是 trace_replay,device-lost 由按 HOME 触发) | PLAN-P12 §3 | +| 出口门 (b) | ❌ **另一台机器**的 client 经 TCP 在 Redmi 入世界,并记录 P6.5 必测数(本轮 client 是 `adb forward` 环路) | PLAN-P12 §3 | -`MEASUREMENTS.md:342` 的「分块后的逐 blob 字节分布尚无新测量」据此可关闭:门 8 第二轮给了 -默认预算下的分布(`maxrec=1808`,稳态 `seg/wrec ≈ 109 B/条`),结论是**默认配置下该分布就是未分块的 -原始分布**(分块在这两条负载上没有机会生效,由 `MOBILEGL_IPC_STAGE_MB=1` 的负控独立证明:seg 运行 -总量两臂逐字节相同 2 698 026 763,`wrec` 仅 +0.11%)。 +## 4. 在跑 -负控(各跑红一次):S1 不可解析镜像(sm)、S2 kill server(dl)、S3 未洗环境(sm)、S5 窗口 token -到达(cp)、S6 **未跑——`LinkTerms.dataPlane` 不存在于树上**(`protocol.fbs` 无 `LinkTerms` / `Refuse`,hs 只落了 build stamp、`abiMajor/Minor` 补检与真实 pid;此处此前写"已跑红一次"是错的,随契约 §2 三行移交 P6.5,契约 §10.3)、S7 裸 abort(dl 普查门)、S8 `SessionFaultCount()==0` -整轮(t6)。 +无。 -### 2.1 出口门之外仍欠(2026-09-22 核出;出口门 1–8 本身已全部达成) +## 5. 下一步 -- server 进程侧的 `PipeStats` 半边是**结构性零**:`ServerMain` 不跑 `MobileGL::Initialize`,`PipeStats::Init()` 从未在 server 进程执行,~80 处 `if (Enabled())` 永远为假,`srv=0 srvpark=0` 长得和"从没等过"一模一样(`PipeStats.h:256` 自己写明了这一点)——门 8 的"socket 门铃 vs condvar"因此没有 server 半边。**已修(工作树未提交,见 §1.1)**:在 server 角色初始化的唯一收口点于 config 加载与角色声明之后加 `Init()`,`loop.Stop()` 之后加 `Shutdown()`,汇总行落 `.server.log`;spawn server 汇总行 0 → 29 条,SSIM 1.0 不变。 -- 184 符号棘轮 CI 门(契约 §12.1):`test.yml` / `scripts/` 里**没有**。 -- 契约 §2 表 0 分给 hs 的 `LinkTerms` / `Refuse` / `wireFingerprint` 拆分:**未落地**,移交 P6.5(`ROADMAP.md` P6.5 行,契约 §10.3)。 -- `MOBILEGL_IPC_IDLE_EXIT_S`(`ARCHITECTURE.md` §15.3 默认 30):未被解析。 -- `TextureParamsWithoutASamplerView` 的"P6 inspection forwarder"债(`MEASUREMENTS.md` §7.2):场景代码已无 P6 引用,是否已由 fc / cp 关闭**待核**。 +1. **真机复测**(ID-P12-13):重建 APK(控制修订 3),重跑七项检查,外加"推流中按 HOME:回调返回前 surface 已释放,并记耗时";在与原门同版本的 CMake/CTest 上复跑一次主机门。 +2. **出口门**:先 (b) 跨机 TCP 入世界 + P6.5 必测数,再 (a) FCL + 杀 server。 +3. **收尾**:写 `MG_Remote/CONTRACT-P12.md`;修 `notes/p12/gate.sh` 的 G1 符号比较(与基准不同源,会印出误导的 `added=N removed=N`);按惯例做一次收官审查。 +4. **阶段表行里的余项**(交接时在树上核过,均未做):in-process server 的 `unix:` 监听、DirectGLES `g_Display` / `g_Surface` / `g_Context` 去全局、cached-app freezer、多 context、FCL env 与 plugin 开关表接线、D8 窗口种类白名单。 +5. 其后按 [`ROADMAP.md`](ROADMAP.md):monolith 跑道 P8;IPC 跑道 P9 → P10 → P11;P6.5 残余(39 例 device 矩阵 + 必测数)与 P3b/P4b 余项并行;`CONTRACT-P7.md` §12 的记录债按阶段认领。 ---- +## 6. 阻塞 / 需要人 -## 3. 真机验收(Redmi `2f7cbe2e`) +- 没有需要用户决策的阻塞。真机复测需要一台装有 NDK、连着 Redmi 的机器(审查轮所在的云容器没有)。 +- 已知环境敏感项:`IterationRPProgram203Scenario`(`integration-magma-full-split`)偶发红,按 ID-P7-22 / ID-P12-12 读作"既有、环境敏感"(钉 lavapipe ICD 即绿),不阻塞 P12。 -trace APK 以 `-Pmobilegl.buildDisaggregated=ON` 构建,调试签名安装。功能全绿: +## 7. 真机与证据 -- **`OpenRA.DirectGLES` spawn**:ssim=1.000000、mismatchPixels=0、`Fatal{` 零条,连跑三次; - `--require-spawn` 确认 `server role in pid N`。 -- 另有 6 个 case(含 in-world、BSL 光影)spawn 下通过。 -- **monolith** 控制组 ssim=1.0;证明门确认 monolith 只写 `mobilegl.client.log`、无 server 日志。 - -**如实记录、非 P6 引入**:`DirectVulkan` 在真机分离路径下画面分歧且**不确定**(inproc 与 spawn -抽自同一组结果:0.988998/0.976494…,而同 pbuffer 的 monolith 是 1.0)。两种 transport 表现一致, -故是 Magma 分离路径在真实 GPU 上的问题,P6 既未引入也修不了;桌面 lavapipe 上 DirectVulkan spawn 为 1.0。 - -那个会杀死 server 的 llvmpipe case(`iterationrp`,`LLVM ERROR: Cannot select vcvtps2ph`):dl 落地后 -从 123 秒的错误 `BarrierTimeout` 变成 **5.7 秒**的正确 `DEVICE LOST`(`revents=0x2010` = POLLRDHUP|POLLHUP)—— -仍红(驱动 bug),但红得诚实。 - -### 3.1 配对 A/B(门 7,2026-09-22) - -reboot-clean + 同热窗口,臂 = `inproc` vs `spawn`(`monolith` 作基线行)。负载 -`minecraft-1.21.4-rd12-odinlite-in-world` + `DirectGLES`(~1471 draws/帧), -`--benchmark-tail-frames 200`,主指标 = client 线程自身 `CLOCK_THREAD_CPUTIME_ID` 的稳态 -p50 ms/帧。两轮会话,各自一个 boot id、各自一次 `adb reboot`(boot id 变化即为事实): - -| 会话 | boot id | 臂序 | 重复 | 结果 | client CPU p50 ms/帧 | -|---|---|---|---|---|---| -| 1 | `e69d0329…` → `107f24d3…` | `monolith,inproc,spawn,inproc,monolith,spawn` | best-of-3 | 18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal{` | inproc **7.880 / 7.889**、spawn **7.876 / 7.869**(均值 **−0.15%**,符号随顺序翻转,臂内散布 **0.2–4.2%**),monolith **约贵 30%** | -| 2 | `107f24d3…` → `7e6c6e9f…` | `inproc,spawn,spawn,inproc` | best-of-2 | 4/4 OK、8/8 `PINNED`、0 `Fatal` | inproc **7.987 / 7.989**、spawn **7.992 / 8.009**(**+0.16%**,臂内散布 0.14–0.69%) | - -**判读:tie。** 两个方向的差值都在臂内噪声之内,且两轮之间绝对值漂移(~0.1 ms)大于传输差本身。 -按本阶段「性能只记录、不设门」的纪律,这组数**如实记录、不设门**,不写成"spawn 更快"也不写成 -"spawn 更慢"。臂证明(契约 §9.5):每个 spawn 条目都记了子进程 pid 与 `transport=spawn`, -inproc 臂带 `Config: IPC` 行,两个 monolith 臂**既不**带 transport marker **也不**带 `Config: IPC` 行 -(控制组 stayed a control)。逐臂 pin 证据、温度范围(会话 1:37.6–46.5 °C;会话 2:37.2–44.9 °C) -与全部原始行见 [`notes/p6/gate7-device-ab.md`](notes/p6/gate7-device-ab.md)、 -[`notes/p6/gate8-doorbell-device.md`](notes/p6/gate8-doorbell-device.md)。 - -### 3.2 门 8① 的 socket 门铃 vs `inproc` condvar - -| 半边 | inproc | spawn | -|---|---|---| -| client `cli` waits/帧 | **121.1**(run total 30 407) | **121.1**(run total 30 401),**两传输逐字相同** | -| client `clipark` parks/帧 | 5.60 / 5.75 | **6.07 / 6.08**(唯一 socket 略差的列,每帧多约 0.4 次 park) | -| server `srv` waits/帧 | 10 866–11 963 | 10 415–10 416(低 8.9–12.9%) | -| server `srvpark` parks/帧 | 5.0–6.2 | 4.66–4.90 | -| server park 率 | 0.042–0.06% | 0.042–0.047% | - -**判读(诚实)**:socket 阻塞读**既不更便宜也不更贵**——client CPU 差 +0.16% 在噪声内,契约 §9 -「阻塞读可能更便宜」**未兑现**(既未被证实也未被证伪,它该移动的那个可观测量没有向任何方向移动)。 -spawn server waits 少 8.9–12.9% **归因于 handoff 纪律而非门铃本身**(两条臂统计的不是同一个事件, -可比的是 park 率,而 park 率两侧几乎相同)。这组数只记录、不设门。 - -**读法警告**(结构性零,不是测量):spawn 的 `srv` 对在 **server.log**、`cli` 对在 **client.log**; -client 行上的 `srv=0 srvpark=0` 是「另一个进程」而不是「从未等待」,server 行上的 `cli=0 clipark=0` -同理。spawn client **从不调 `OnPresent`** → 窗口字段 `frames=0 window=0` 无效,但 `PublishGauge` 是 -store,所以 `cli`/`clipark` 的 **run total 有效**;spawn client 只有 `Shutdown` 终行(benchmark 模式) -或 0 行(snapshot 模式)。 - -### 3.3 门 8②③ 的 wire 计数器(`SEG_STAGE` 字节/帧、分块后记录/帧) - -inproc、真实 trace retrace,`MOBILEGL_PIPE_STATS_PERIOD=1`: - -| 负载 | `SEG_STAGE` 字节/帧 | 记录/帧 | -|---|---|---| -| OpenRA | **130.8 KB median** / 8.01 MB steady mean(含启动负载 8.07 MB) | **211 median** / 206 mean | -| rd12 in-world | **816.2 KB median** / 1.53 MB steady mean(含负载 10.79 MB) | **7 497 median** / 7 504 mean | - -**framing 警告**:PERIOD=1 下首窗含启动负载(rd12 帧 1 有 981 654 draws vs 稳态 ~1 471), -run-wide mean 约 **7×** 稳态,所以两个读数都报,引用时必须说明口径。**`maxrec=1808` 与分块前相同** -——默认 8 MiB chunk 预算在这两条负载上**从未被打到**。负控 `MOBILEGL_IPC_STAGE_MB=1`(256 KiB 预算): -`seg` 运行总量两臂**逐字节相同**(2 698 026 763),`wrec` 仅 **+0.11%**——证明计数器测的是生产者而非切分; -同一 A/B 下 `tex` **+194%** **未解释**(`MOBILEGL_IPC_STAGE_MB` 同时是 arena 大小与 chunk 预算的来源, -未隔离到单变量),**记为证据、不作结论**。逐 blob 分布首测(integration 车道 JSON;retrace 车道拿不到 -dump,因 snapshot 模式不走 `cleanUp()` → `MobileGL::Destroy()`):**70 records、8 blobs、bucket 3–12**。 - -**做与未做,分开写**:OpenRA 真机辅负载**未跑**(如实记「未跑」); -[`notes/p6/gate8-wire-counters.md`](notes/p6/gate8-wire-counters.md) 的 §6 数据全部来自 WSL + llvmpipe, -门 8②③ 是**桌面 inproc** 数,门 8① 的 server 半边另有真机重复(§3.2)。 - ---- - -## 4. 日志按角色分文件(本阶段的一处基础设施改动) - -spawn 的两个进程原先 O_APPEND 追加同一个 `MOBILEGL_LOG_FILE_PATH`(正确但两个会话混在一处)。现在 -库按**调用线程角色**写 `.client.log` / `.server.log`(inproc 下 apply 线程即 server 角色, -同样分开)。基名不再指向任何文件——这是刻意的:没跟上改名的读取方会「文件不存在」当场报错,而不是 -半读通过。34 个消费方全部改到派生路径;拒绝普查读两份;派生规则由库 `RoleLogPath` 导出,不复制。 -G1 守卫下 pull 构建不变。 - ---- - -## 5. 已知未决 / 明确不属于 P6 - -- **`DirectVulkan` 真机分离路径分歧**——Magma × 真实 GPU,非 P6。见 §3 与 `ROADMAP.md` P7 行。 -- **P6.5**:传输栈两轴化(控制面 `ITransport` × 数据面 `ILink`,握手协商、可混搭)+ TCP 跨机 + wire 定宽与布局摘要;`StreamLink` 已声明未接线;`SEG_STAGE` 送窗口复活。终局已改为 TCP 跨机跨平台(2026-09-22),AF_VSOCK 撤回;见 `ROADMAP.md` P6.5 行、`ARCHITECTURE.md` §11.9。 -- **Ph**:让 `Session::Fail` 可返回的策略翻转(需在每个依赖 `[[noreturn]]` 的站点造真实返回路径)+ 配对 / 认证;前置于 P12 的非 loopback 监听。 -- **P12**:真窗口跨进程(`ANativeWindow*` 是客户端进程内指针,`SetWindowHandle` 至今具名拒绝)。 -- **seq/op 未穿 SessionFault 帧**:90 个站点不逐一穿线,消息本身已带 op,帧只带 code/family/message。 - -两条**结构性限制**,随收尾一起生效、如实记录(不是缺陷但会绊住读者): - -- **spawn client 无 `OnPresent`** → 它的窗口字段 `frames=0 window=0`,于是 - `draws=` / `wrec=` / `bytes[...]` / `gates[...]` 与**所有 per-frame 字段**在 spawn client 上都印 0 或 - `n/a`,**无效**;但 `PublishGauge` 是 **store 不是 add**,所以 `cli` / `clipark`、`maxrec` / - `maxcap` / `ringwraps` / `ringpads` / `ringwaits`,以及发射侧直接 `Add` 的 - `resid` / `csob-blob` / `seg` / `wrec` 的 **run total 有效**(窗口归属只有最后一段)。要取 spawn 的 - 窗口化 client 数必须让 client 进程到达一次帧边界——那会与 backend 的帧边界重复计数,需单独判断, - 不在本阶段。**逐 role 的取数规则**因此是:`srv` 对读 server.log、`cli` 对读 client.log, - spawn client 只有 `Shutdown` 终行(benchmark 模式)或 0 行(snapshot 模式)。 -- **`MOBILEGL_PIPE_STATS_FILE` 已改角色派生**:dump 与 banner 都写/印 - `.client.json` / `.server.json`,**基名不再指向任何文件**(与日志 sink 同一套 - `RoleLogPath` 规则,刻意让没跟上的读取方当场 `ENOENT` 而不是半读通过)。inproc 只产出 - `.client.json` 是**正确**的(服务端角色是线程不是进程,`Shutdown()` 只跑一次)。仓库内目前没有 - 这样的读者(`grep` 确认),但任何新写的车道/脚本都要读派生名。语义未变:仍是「在这里写 dump」, - 只是「这里」由库按角色展开成两个名字。 +- 当前 APK `26.09.29b7b28-trace`(控制修订 3,与 P7 终局窗口的 p7w8 server 不兼容);设备 Redmi `2f7cbe2e`(Adreno 830),定频协议 [`devices/pin-verification-2026-09-07.md`](devices/pin-verification-2026-09-07.md)。 +- 真机证据 `notes/p12/device/`(`evidence-strip.png` 九格拼图,`trial-*/` 是按 head 归档的旧轮次);主机门日志 `notes/p12/gate/`;设备脚本 `notes/p12/android/`。 diff --git a/docs/Disaggregated/CURRENT_STAGE_STATUS.md b/docs/Disaggregated/CURRENT_STAGE_STATUS.md deleted file mode 100644 index 709d963f..00000000 --- a/docs/Disaggregated/CURRENT_STAGE_STATUS.md +++ /dev/null @@ -1,85 +0,0 @@ -# 当前阶段状态 - -> 这一页只回答一个问题:**现在在哪、什么已落地、什么在跑、下一步是什么**。随每次合并 / 推送 / 真机窗口更新;历史与设计不在这里(设计见 [`ARCHITECTURE.md`](ARCHITECTURE.md),阶段表见 [`ROADMAP.md`](ROADMAP.md),逐条裁定见 [`notes/p7/INTEGRATOR-DECISIONS-P7.md`](notes/p7/INTEGRATOR-DECISIONS-P7.md),规范见 [`MG_Remote/CONTRACT-P7.md`](../../MobileGL/MG_Remote/CONTRACT-P7.md))。 - -**阶段:P7 DirectVulkan(Magma)全量迁移——✅ 已收官(2026-09-23,ID-P7-63)**,并行流 **P3b/P4b 深化(Espryt,wave 2-D)** 与 **Ph 小件(簇 F)**。计划 [`notes/p7/PLAN-PH-P34B-P7.md`](notes/p7/PLAN-PH-P34B-P7.md)(五波)。 - -**更新:2026-09-24(P12 子集)** · 计划与交接 [notes/p12/PLAN-P12.md](notes/p12/PLAN-P12.md)。**P12「server 自有屏幕窗口」子集已实现、未收官,已并入 `feat/disaggregated`**(15 提交自 `9f669e52` 起,加审查轮 10 个问题的修复——两个大项:批内失窗、会话间 Espryt 单元影子悬空;裁定 [notes/p12/INTEGRATOR-DECISIONS-P12.md](notes/p12/INTEGRATOR-DECISIONS-P12.md) ID-P12-5–15,主机门与 G1 见 ID-P12-14/15,**审查轮之后未上真机复测**,ID-P12-13):Android 上的 server 自建 `ANativeWindow`(自己的 SurfaceView)把渲染流**上屏**,离屏路径保留,同一时刻只一条活跃;client 以新的 `WindowKind::ServerOwned` **完全无头**接入(控制修订 3)。主机门除一条既有环境敏感项外全绿,G1(pull 构建 `.text` `0xa52203`、符号 0/0)已独立复核;真机 Redmi `2f7cbe2e` 上七项编号检查过了双后端上屏、串行/Busy、失窗 device-lost、离屏不回归、无显示具名拒绝、一台设备一个 server。**两个出口门都还没打**:跨机 TCP(门 b)完全未做,FCL + 杀 server 的 device-lost(门 a)未做——本轮的真机是 trace_replay 经 `adb forward` 环路驱动的。上一阶段 **P7 已收官(2026-09-23,ID-P7-63)**:§8 九项全绿——终局窗口 3(`p7w8-78e71f2b`)门 3 PASS 36/36、门 5 PASS;门 4 以 `# P13` 读法关闭。 - -**更新:2026-09-23(ID-P7-49–63)** · 交接 [notes/p7/HANDOFF-2026-09-23.md](notes/p7/HANDOFF-2026-09-23.md)。**P7 收官**:§8 九项全绿。CI 七根因修、F2、门 5 三缺陷、codex 收官审查修复、spawn 冷启动心跳(协议修订 2)均已落地推送。下一阶段按 ROADMAP:P8 / P9 / P12 余项。 - -## 1. 出口门总览(CONTRACT-P7 §8 的 9 项分母) - -| # | 子系统 | 状态 | 证据 | -|---|---|---|---| -| 1 | Magma 两进程车道三臂同数绿 | ✅ | M3 集成树门:magma-split/spawn/tcp **102 / 81 / 71**,full-split **540**;OpenRA DirectVulkan inproc + spawn 回放 1.000000、0 unsound | -| 2 | §3.2 六个 `@P7` 退役 | ✅ | A(byte-tail、native-range、对齐 decline)、B(copy-image、default-color-blit、mip 半退役)、B2(multisample shape/aspect)、C(vertex-layout 拆分);树上 `@P7` 拒绝站点 **0** | -| 3 | `StateObjectDeathOps` | ✅ | C,`dce9953d..cf7ca59f` | -| 4 | 烘焙 (A)(D) + (B′) | ✅ | B 深度 mip 烘焙;B2 disaggregated 构建的 monolith 臂改用烘焙模块 | -| 5 | OQ-8 反射归档 `storageBlocks` | ✅ | C | -| 6 | OQ-10 `kCapResidentSubData` 按 server 表发布 | ✅ | C | -| 7 | verify × split(门 2) | ✅ | B4 集成树 verify build:unit 2439、integration-verify 1152、integration-verify-split 1086,全绿;V1/V1 r2 负控与 8 verify trace 证据仍有效 | -| 8 | 真机 ssim 1.0(门 3,分母 36) | ✅ | 终局窗口 3(`p7w8-78e71f2b`)**36/36**:33 例四臂逐位同,三例 Iris 按 ID-P7-62 分布判据通过,OpenRA 四臂 1.000000(ID-P7-63) | -| 9 | CTS 五块 AFTER ≤ 0.5 pp(门 5) | ✅ | 窗口 2 与终局窗口 3 均 PASS:shader-image +4.35 pp、texture +0.96 pp、其余持平,0 新 crash、0 流失;UBO(GTF)记 unrun | - -**40% 检查点(§8 中点)已过:6/9**,不触发重定基线。G1 全程恒等(pull `.text` `0xa52203`、符号 0/0);census 79 站点 / 0 未标;棘轮 186 → 173 → **171**(ratchet88:`p7-magma` 余 86 全标 `# P13`,门 4 关闭,ID-P7-55)。 - -## 2. 已落地(`feat/disaggregated`,按合并序) - -| 波 / 包 | 内容 | 落地 | -|---|---|---| -| wave 0 | Magma 两进程车道、`link_ratchet.py`、普查门扩容 + `MGPipeSessionFailHook`、设备跑器 `--matrix/--repeat/--archive-dir`、CTS caselists | `origin@801f8eda` | -| wave 1 | 设备窗口 #1(Redmi 2f7cbe2e,DirectVulkan):门 3 分母 36、E0–E7 归因、CTS `$BASE` | `notes/p7/device-window-1/` | -| X1 | persistent-map tracker 在 arm64 不认领 fault(bionic 指针标记 0xb4) | `4cbf78c2` | -| F1 | split 起会提到 `MG_State::Init()` 之前 + `Fatal{CapsBeforeFirstSnapshot}` | `df17c416..3179c497` | -| C | Magma `StateObjectDeathOps`、OQ-10、vertex-layout 拆分、OQ-8 `storageBlocks` | `dce9953d..cf7ca59f` | -| A | UBO/SSBO/texel 对齐与 native-range 行 | `450d3075..5937b4fc` | -| B | mip 形状半退役、深度 mip 烘焙、copy-image、default-color-blit、wire 回读 fence | `c4be3800..76b74693` | -| D1 | XFB 两洞、回调 10→9、resolver 具名 stop、readback 三臂 | `faf0a1f9..ae3f43ab` | -| D2 + 返工 | `TextureViewAlias`、TUS 门、memo 纯度门、`SubsystemDeps.def`、场景普查 | `da30c130..519445f9`、`b3334cf3..a2d7fbd7` | -| D3 + 审查修复 | view 陈旧缺陷(clean gate 读 storage record)、D-K2 单一陈述 | `1e1f4bad..00d02396`、`22761a69` | -| B2 | multisample 两处退役、AllocatorDebtScope ×2 删、(B′)、棘轮 173 | `452d33b5..3c2867d3` | -| B3 | **OpenRA 真因**:wire 臂 completed-frame-serial floor 不健全 → 有序路径 + 可证 floor + 32 处静默出口具名 | `76bbb6c3..9a494b6f`,`origin@3c80cd62` | -| B2 返工 r1 | tcp 五条 server 端旋钮条目改 split+spawn(parity 例外 `MAGMA_SERVER_ENV_KNOB_NO_TCP`)、翻转 MS 深/模板 resolve 逐行镜像、缩放 / X 镜像 / 跨格式 decline、`MsFlip.` 用例 | pipe `9fed478d..cc3c18c5`(审查 rework → r2 在跑,ID-P7-35) | -| B3 返工 | `WireDeclines.def` 53 行全站点 + 审计脚本、`StaleSerial.` parity 例外、注记 + `:273`/`:432`、`run_trace_case.cmake` 对 `unsound-serial-complete` 打红 | pipe `b8d10d07..75a64ce0`(审查 land with fixes → 修复轮在跑,ID-P7-36) | -| V1 | `PipeRespecifyScope` 放宽、`integration-verify-split`(1074 条,双后端 inproc)、两负控 split 臂红、8 verify trace × DV × inproc 0 分歧、比对器 ReadPixels 窗口 oracle 改中性 pack | `origin@87584d0b`(审查 land with fixes → 修复轮在跑,ID-P7-37) | -| B2 返工 r2 | 用例按 `IsSplitLane()` + 后端名 skip、逐行拷贝行距对齐 + stencil 腿、`DeclineWireDepthStencilResolveShape` 先决定、`MsFlip1.`、parity tail 恰匹配 | `origin@87584d0b`(审查 land with fixes → r3 在跑,ID-P7-38) | -| B3 修复轮 | 审计剥注释 / 字面量 + 按块回溯 + `--self-test`、spawn 无 server 日志 FATAL、注记双行 + `WaitForFrameSerial` 债 | `origin@87584d0b`(审查 land with fixes → 修复 r2 在跑,ID-P7-38) | -| F 片 1–3 | 常量时间令牌 / ≥16 字节 / 无令牌只 loopback / smoke 进 CI;`Welcome.dataNonce` 绑定(TCP 250 ms 配对窗口消失,指纹变更);PH-8 钳制证明 | `origin@87584d0b`(审查 land with fixes → 修复轮在跑,ID-P7-39) | -| E1 | Iris 普查 231/231 全活、70/77 逐字节同、P9 例外表空;§7.2 同名重跑 24 绿 / 3 具名停止 / 0 错答 | `origin@87584d0b`(docs only,ID-P7-38) | -| M2 | 孤儿 wire store 在 defer 路径回收 + `MOBILEGL_IPC_WIRE_DEFERRED_MB` 水位线 + 1,024 计数上限 + `wbuf[]` 计量 + `MagmaWireReclaimScenario`;主机 bsl spawn server 825 → 546 MiB、映射 41k → 5.8k | pipe `b81a8827..7d131527`(门全绿 magma 100 / 79 / 71,审查 land with fixes,ID-P7-43);**r2** = memo destroy-epoch,must-fix 关闭:pipe `4d207468..f62703e9` + parity `1ab62ece`(门全绿 magma 101 / 80 / 71,集成者审查 land,ID-P7-46) | -| F 修复轮 | hand-off 端绑定后关 + `MSG_DONTWAIT`(多余 DataBind 具名拒绝)、listener 绑定后关、`sha256(protocol.fbs)` 钉到控制修订、定宽令牌比较、超额 Hello 记警、`MalformedHello`、文档更正 | pipe `0293aebb..9cea5140`(门全绿,集成者审查 land,ID-P7-44) | -| B3 修复 r3 + B2 r4 | 审计跨 `#endif` 只放行 disagg 守卫 + 缩进 `#else` 停走(16/16)、`EVIDENCE` 保护、tcp 信息;窗口腿独一底色、去死合取项、按符号引用 | pipe `bd849b28..4b96f55d`(门全绿,集成者审查 land,ID-P7-44) | -| M3 | 长帧 descriptor set 游标按 queue-idle 证明回卷,2049 SSBO 窗口用例 split/spawn 绿且 red-once 红;包树与 pipe 完整门绿 | pipe `9fd768d4`(ID-P7-50,集成者审查 land) | -| B4 | 聚合 submit 前缀等待、frame serial 重新扫描、Present 槽退休保护、pre-pass pending、texture prune 与 staged extent 清理 | pipe b19297d5(ID-P7-51;集成者 review land,完整 pipe 门通过;当前文档/citation/push 收尾中) | -| CI 修复 | 链接缝走工厂、TCP fixture headless + 资源锁守卫、D3 拒绝句、Magma-only decline、dual-block 登记、retrace 链(旋钮 / pull verify 日志 / spawn 冷启动预算)、并发组、80 个标题缩短 | pipe `eaef83b3..e751b07b`(ID-P7-56) | -| ratchet88 | `MGPipeTextureLegacyArmScope` 退役、余 86 标 `# P13`、棘轮 171 | `d49df354` / `d74e9923`(ID-P7-55) | -| F2 | Ph 余项:PH-1 (3)(4) 每会话闩、PH-6 事件通道弃投闩、PH-7 (5) fork 前认证 + 预认证有界 + 退避、fuzz 三臂进 CI;PH-4 两个回归(零尺寸层、`R8 == 0`)与 ID-49 回读在线内修掉 | F2 线 29 提交 + 集成 2 跟进(ID-P7-57) | -| 门 5 修复 | 超槽 ReadPixels 切带、深度/模板 resolve 臂由设备探针选(+ POST FIXED 行)、单采样深/模板 blit 改 copy、越界图像单元绑占位 | ID-P7-58 | -| 窗口 2 工装 | 门 3 / bsl / CTS AFTER 一条命令、可续跑、共享设备锁;判读按合同公式、五块必齐、同一 boot_id | ID-P7-59 | -| codex 收官修复 | 越界 / 空图像单元 nullDescriptor 或按单元私有占位、PH-5 最小编码宽度、闩 pop 前再查、探针按设备缓存、判读器硬编码 §7.2 常量 | ID-P7-60 | -| spawn 冷启动心跳 | server 执行 surface 操作期间发 `SurfaceProgress`,client 预算量沉默(封顶 120 s);控制协议修订 2 | ID-P7-61 | -| 门 3 判读 + blit | monolith 不稳定的 case 用最近邻分布判据、monolith ×5;1:1 LINEAR blit 走最近邻 | ID-P7-62 | - -## 3. 真机(Redmi 2f7cbe2e,Adreno 830) - -- 当前 APK:**p7w8**(`26.09.78e71f2-trace`,控制协议修订 2),supervisor `0.0.0.0:40613`,`/data/local/tmp/mgcts` 为 `$BASE` 库。 -- 终局窗口 3 证据 `notes/p7/device-window-2/final-p7w8/`,窗口 2 `notes/p7/device-window-2/window2-p7w7/`,三例 Iris 不确定性追查在 ID-P7-62。 -- **P12 子集**:APK `26.09.29b7b28-trace`(**控制协议修订 3**,与 p7w8 的 server 不兼容),证据 `notes/p12/device/`(九格拼图 `evidence-strip.png`)。七个编号检查在三个 head 上跑过,`trial-*/` 是按 head 归档的旧轮次。 - -## 4. 在跑 - -无。 - -## 5. 下一步 - -1. **P12 审查轮的真机复测**(ID-P12-13):重建 APK,重跑七项编号检查,外加「推流中按 HOME:回调返回前 surface 已释放,并记录耗时」;并在与原门同版本的 CMake/CTest 上复跑一次主机门(ID-P12-14)。然后 **P12 未收官,先还两个出口门**([notes/p12/PLAN-P12.md](notes/p12/PLAN-P12.md) §3):(b) 另一台机器的 client 经 TCP 入世界并记 P6.5 必测数;(a) FCL 同机 spawn + 杀 server 的干净 device-lost。之后补 `MG_Remote/CONTRACT-P12.md`、清 `ARCHITECTURE.md` 里那条被删掉的旧窗口路径、按惯例做一次收官审查。 -2. `p12/onscreen` 的余项:in-process server 的 `unix:` 监听、DirectGLES `g_*` 去全局、freezer、多 context、FCL env / 开关表接线、D8 白名单(拒 X11 / Win32Hwnd / None)。交接时已在树上核对,全部未做。 -3. 其余按 [`ROADMAP.md`](ROADMAP.md):monolith 跑道 P8(emulation 下放 + 索引宿主镜像);IPC 跑道 P9(反向通道异步 reply)→ P10 → P11;P3b/P4b 余项并行。 -4. CONTRACT-P7 §12 的记录债按阶段认领。 - -## 6. 阻塞 / 需要人 - -- p7w6 TCP 矩阵和设备 monolith 对照已结束;后续门 3/CTS 仍不可同时 force-stop 同包 Service。当前没有需要用户决策的阻塞。 -- X2 已关闭 `InitialCapsStartup` 的测试夹具乱序 flake;不再列为待修。 -- **待裁定(一条,不阻塞)**:`IterationRPProgram203Scenario` 在 `integration-magma-full-split` 上偶尔红。ID-P7-22 已把它改判为**环境敏感**(钉 lavapipe ICD 即绿),`p12-onscreen` 两个 build 目录都已钉 ICD、交接时直跑 3/3 绿、整条车道重跑两遍 558/558 绿。建议给门脚本一个明确读法(钉 ICD 即绿 / 记为已知偶发),别再口头带过。另:`~/w7/notes/p12/gate.sh` 的 G1 符号比较与基准不同源,会打出 `added=N removed=N` 的误导行,P12 收尾时一并修(见 PLAN-P12 §4)。 diff --git a/docs/Disaggregated/MEASUREMENTS.md b/docs/Disaggregated/MEASUREMENTS.md index c2102fd3..67d9afcf 100644 --- a/docs/Disaggregated/MEASUREMENTS.md +++ b/docs/Disaggregated/MEASUREMENTS.md @@ -1,855 +1,134 @@ -# 实测记录 +# 实测记录(摘要) -> 每张表写明设备、提交与命令。设备:`35d0befa` = Xiaomi 24129PN74C(SM8750 / Adreno 830v2)与 `3B159D009VZ00000` = Oppo PLG110(MT6993 / Mali)用于 P0–P3a;`2f7cbe2e` = Redmi M332BF(SM8750 / Adreno 830v2,与小米同 SoC 同定频点)是 P4a 起唯一的 A/B 设备。定频、风扇与热协议见 `devices/pin-verification-2026-09-07.md`。 +> 每个阶段只留头条数字与结论。完整的表、命令、协议与读法在对应 `notes/<阶段>/README.md` 的"实测"节,**小节编号 §N.x 原样保留**(代码与旧笔记里的 `MEASUREMENTS.md §7.2` 这类引用,按下面每节的"全文"链接去找)。 > -> 口径(用户 2026-09-08 起):性能对着 pull 臂**记录**、不作阻塞门;正确性门是硬门。两条读数纪律:`acc/draw` 是约 10 个热入口的**静态**计数,把读点搬走与把工作去掉在它上面长得一样,判性能只看 CPU 时间序列与六个 memo 门;`ctest -V` 做拒绝普查是假零(console sink 在发布配置里被编译掉),必须逐用例读私有日志。 +> **设备**:`35d0befa` = Xiaomi 24129PN74C(SM8750 / Adreno 830v2)与 `3B159D009VZ00000` = Oppo PLG110(MT6993 / Mali)用于 P0–P3a;`2f7cbe2e` = Redmi M332BF(SM8750 / Adreno 830v2)是 P4a 起唯一的 A/B 设备。定频、风扇与热协议见 [`devices/pin-verification-2026-09-07.md`](devices/pin-verification-2026-09-07.md);Redmi 的 GPU 钉频点随时期变过(2026-09-11 为 1050 MHz,P5b 时 1100 MHz,P6 起本板实测钳在 1050 MHz),跨钉频口径的活动不可比钟频,只有同会话配对可比。 +> +> **口径**(用户 2026-09-08 起):性能对着 pull 臂**记录**、不作阻塞门;正确性门是硬门;主指标是**逐线程 CPU 时间**。三条读数纪律:`acc/draw` 是静态下界,判性能只看 CPU 时间;拒绝普查必须逐用例读私有日志(`ctest -V` 是假零);spawn 下 client 不推进帧窗口,窗口化字段只在推进帧的那条日志上有效。 -## 1. P0(2026-09-05,trace APK `7ef7c7e5`,spike `8a239177`) +## 1. P0(2026-09-05) -### 1.1 Spike A — 从应用自身进程 exec 第二个原生可执行文件 +- spike A:Adreno 与 Mali 上,`untrusted_app` 进程 `fork`+`execve` `libMobileGLServer.so` 均 OK(同域、零 avc denial)。 +- spike B:**T0**(AHB BLOB 交接)是唯一在两设备、两后端上完整读写的档;T1 opaque fd 仅 Adreno;GLES memobj 在 Adreno 上 map 失败;host-pointer 在 Mali 只读。 +- 计数器基线:真机稳态动态 accessor 每 draw 6.5–9.3 次;Magma 在 MC 26.3 每帧重打包 331 KB 具名 UBO;同样 185 次纹理发射,Espryt 整 box 635 KB/帧 vs Magma rect 40 KB/帧。 -| 设备 | 结果 | -|---|---| -| Adreno 830 | **OK**:`untrusted_app` 父进程 `fork`+`execve` `/libMobileGLServer.so`,子进程同域同 category、exit 0、`execErrno=0`、零 avc denial | -| Mali | **OK**,同形 | - -主机侧已证:AGP 会把改名成 `lib*.so` 的 `add_executable` 打进 `lib/arm64-v8a/`(`RUNTIME_OUTPUT_DIRECTORY` 重定向到 `CMAKE_LIBRARY_OUTPUT_DIRECTORY`);`posix_spawn` 在 minSdk 26 不可用;应用进程 stdout/stderr 是 `/dev/null`,子进程用 marker 文件证明自己活过。代码:`tools/spikes/server_stub/`、`android-plugin/app/src/trace/cpp/spawn_spike.cpp`、`MOBILEGL_BUILD_SERVER_SPIKE`。 - -### 1.2 Spike B — 跨进程外部内存分档(`tools/spikes/extmem_probe/`,`shell` 域,4 MiB payload,每行一次真 GPU 访问 + 两侧字节校验) - -| 路线 | Adreno 830 | Mali | -|---|---|---| -| T1-opaque-fd(server 导出 `VkDeviceMemory` fd,client `mmap` + 导入) | **OK** 完整往返 | UNSUPPORTED(`VK_ERROR_INVALID_EXTERNAL_HANDLE`) | -| T1-dma-buf | UNSUPPORTED | UNSUPPORTED | -| T1-gles-memobj-fd(`GL_EXT_memory_object_fd`) | **FAIL**:导入接受,但每次 `glMapBufferRange` → `GL_INVALID_OPERATION` | UNSUPPORTED | -| **T0-ahb-blob-transfer**(client 分配 `AHardwareBuffer` BLOB → socket 交接 → server Vulkan + GL 导入) | **OK** 全链 | **OK** 全链 | -| T3-external-memory-host | UNSUPPORTED | PARTIAL:GPU 写对宿主映射不可见(只读档) | - -决定:唯一在两台设备、两个后端上都是完整读写的档是 **T0**;Adreno 另有 T1(Vulkan 路径);Mali 无任何 server 导出路线。Caveat:运行域是 `shell` 不是 `untrusted_app`(`ROADMAP.md` 开放问题 3)。 - -```sh -ANDROID_NDK=$HOME/android-sdk/ndk/27.3.13750724 tools/spikes/extmem_probe/build_android.sh /tmp/extmem-build -adb -s $S push /tmp/extmem-build/extmem_probe /data/local/tmp/ && adb -s $S shell "chmod 755 /data/local/tmp/extmem_probe && /data/local/tmp/extmem_probe" -``` - -### 1.3 边界计数器基线(`MOBILEGL_PIPE_STATS=1`,最后一个完整 120 帧窗口;accessor / memo 数字是软件确定的,两台设备相同) - -| trace(窗口内帧数) | 后端 | draws/f | acc/draw | buf B/f | tex B/f(发射 box / rect) | ubo-global B/f | ubo-named B/f | memo 门(hit/miss) | -|---|---|---|---|---|---|---|---|---| -| `minecraft-1.21.4-in-world`(360) | Espryt | 91.6 | **9.28** | 13.5 K | **635 K**(185 box / 0 rect) | 16.7 K | 0 | ers 9257/2577,etl 10538/1296,eub 10720/1114 | -| 同上 | Magma | 91.6 | **8.56** | 13.5 K | 39.9 K(97 box / 89 rect) | 16.7 K | 0 | mfp 0/10994,mpm 9240/1754,mdt 9120/1874 | -| `minecraft-1.21.4-fabric-iris-bsl-in-world`(120,memo 冷) | Espryt | 23.2 | 21.04 | 32.6 K | 8.8 K | 1.8 K | 0 | ers 1958/1843,etl 722/3079 | -| 同上 | Magma | 23.2 | 11.26 | 313 K | 256 K | 1.8 K | 0(vtxc 1.7 K) | mpm 1890/895,mdt 1573/1212 | -| `improved-transparency-minecraft-26.3`(1200) | Espryt | 1320 | **8.44** | 333 K | 0 | 0 | 0 | ers 156925/2791,etl 148606/11110,eub 148246/11470 | -| 同上 | Magma | 1320 | **6.53** | 173 K | 0 | 0 | **331 K** | mfp 21360/137036,mpm 134421/2615,mdt 156611/1785 | - -门缩写:ers = `EsprytRenderState`,etl = `EsprytTextureSyncList`,eub = `EsprytUnitBindingsEpoch`,mfp = `MagmaDrawFastPath`,mpm = `MagmaPipelineMemo`,mdt = `MagmaDynamicTail`。读法:真机稳态动态 accessor 成本是每 draw 6.5–9.3 次(预测区间 10–25 的下沿),推送要打败的是 ~8 次 accessor + memo 探测;Magma 在 26.3 每帧重打包 **331 KB** 具名 UBO 字节(D-B8);同样 185 次发射,Espryt 整 box 路径 635 K 纹素字节 / 帧、Magma rect 路径 40 K,16×("server 选上传形状"的依据)。 - -```sh -ANDROID_SERIAL= MSYS_NO_PATHCONV=1 python3 tools/trace_replay/run_android_retrace_local.py \ - --case minecraft-1.21.4-in-world --backend DirectGLES --env MOBILEGL_PIPE_STATS=1 --env MOBILEGL_PIPE_STATS_PERIOD=120 -# 数字在结果目录的 mobilegl.log 里,grep 'MGPipe stats:' -``` - -### 1.4 桌面数据点与语料事实 - -- llvmpipe / lavapipe 动态 accessor(`GuiBatchScenario`,memo 冷):Espryt 20.65 / Magma 15.54 次 / draw。 -- dirty-surface 面:`MG_Impl/GLImpl` 41 个文件、926 次 mutator 调用、73 个不同 mutator(`RecordError` 占 836 次);92 次位于同函数内也到达后端的即时发布点,其余由紧随的 verb 发布。 -- 读点覆盖:63 个 `PipeInputs` 字段;后端读点清单 → 调用映射 **0 UNMAPPED**。 -- OOM 探测惯用法:41 个 trace fixture 中 0 例(9 次 `glRenderbufferStorage` 无一在 3 个调用内跟 `glGetError`)→ `glRenderbufferStorage*` 不 ack。 -- `FramebufferSrgb` / `DepthClamp`:六个后端读点消费编译期常量 `false` / 零读点;`glEnable` 落到 `default:` 分支既不存储也不报错;41 个 fixture 无一开启。 -- payload 尺寸(`MGPipeTypes.h` 的 `static_assert`,arm64 与 x86-64 一致):`MGPDrawInfo` 56、`MGHostSpan` 32、`MGPBindRenderState` 12、`MGPResourceDesc` 88、`MGPFramebufferState` 304、`MGPProgramDesc` 192、`MGPSubData` 72;`SEG_CMD` 按 56 B 头定尺,MC 帧 1000–4000 draw 时每帧 56–224 KiB 头字节。 -- persistent map 采纳的既有基线(`dev`,MC 26.3,Adreno):p99 163→21 ms、稳态 40→115 fps、省 ~400 MB;P11 的回归上限对着它。Mali 上传作业数悬崖:~100 个精灵 rect 对一个 union box 是 +6 ms/frame。 - -### 1.5 Harness 事实与陷阱 - -- trace app 从不到达 `MobileGL::DestroyImpl`,`MOBILEGL_PIPE_STATS_FILE` 在设备上永远不会写;只有 `mobilegl.log` 的周期汇总行,`MOBILEGL_PIPE_STATS_PERIOD` 为此而加。 -- `run_android_retrace_local.py` 每棵树共用一个 `.trace-work/android-retrace-result` 根并 `rmtree`,两台设备必须从一棵树**串行**跑。 -- `--env` 值里的 `/data/...` 会被 MSYS 路径转换,用 `MSYS_NO_PATHCONV=1`。 -- ColorOS 首次 `adb install` 卡在 `InstallGuideActivity` 确认页;streamed install 进行中点它会让设备掉线几秒。 -- `minecraft-1.21.1-neoforge-create-indirect-in-world` 在两台设备上都失败,`dev@81b17c0b` 基线 APK 复现——`dev` 侧问题(`ROADMAP.md` 开放问题 17)。 +全文 §1.1–§1.5:[`notes/p0/README.md`](notes/p0/README.md)。 ## 2. P1(lavapipe / llvmpipe) -### 2.1 规模 +- 规模:277 个 `pGLContext->` 站点 + 58 行非箭头、63 个 `PipeInputs` 字段、83 条填充点。 +- verify 通道找到 9 处缺填充行与 3 个"verb 内后端改前端"字段(→ push-on-mutation)。 +- 验收:pull 符号 0/0/0/0;单元 1485×3;`integration-verify` 818 零 `Fatal{`;79 例 retrace verify 零分歧。 -| 量 | 值 | -|---|---| -| 后端 `pGLContext->` 箭头站点 | 277(Espryt 113、Magma 164) | -| 非箭头行 | 58(Espryt 9、Magma 49,其中 43 条是 Magma 的逐 verb `MOBILEGL_ASSERT`) | -| `PipeInputs` 字段 / 不同访问器 | 63 / 62(Espryt 32、Magma 56) | -| 填充点 | 83 条 `MGP_FILL`,覆盖 69 个 verb、9 个类 | -| `SyncPersistentMappedRange` / `SyncGpuWrites` | **21**(Espryt 9 + Magma 12;P5 b1 复核,原记 20 漏了共享 helper `ResolveIndirectCommandBytes`)/ 6 | - -### 2.2 verify 通道发现的两类真问题 - -**缺填充行(9 处)**:`kReadback` 缺 `IsTransformFeedback{Active,Paused}`、`kTextureOp` 与 `kDispatch` 缺 `IsCapabilityEnabled`、`kBlitOrCopy`/`kTextureOp` 缺着色器 blit 用到的 viewport 与顶点 / 缓冲绑定。其中 8 行是静态过近似(代码路径可达但通道未跑到),有意保留——能退役它们的证据只能是动态的(完整 CTS caselist + `MOBILEGL_PIPE_POISON_OMIT`,未做)。 - -**verb 内后端改前端(3 个字段)**:Magma 在 draw 里写前端对象(合成回退纹理、材质化排队清除、覆写 sampler filter),8 条 DirectVulkan 用例与 2 条 trace 报 `Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@Draw*"}`。解法 **push-on-mutation**:前端计数器移动时用 `MGP_NOTE_MUTATION(Field)` 刷新推送块里那一个字段——钩子挂在三个计数器上(`BumpSamplingResolutionGeneration`、`BumpTextureBindGeneration` / `NoteUnitTouched`、高水位分支),不挂在四十个写入点上。 - -### 2.3 验收 - -| 门 | 结果 | -|---|---| -| pull 构建符号与 `.text` | 0 增 / 0 删 / 0 改尺寸 / 0 重命名,`.text` 不变 | -| `MG_Backend` 里的 `pGLContext` | 0 | -| 单元(pull / push / verify) | 1485 × 3 | -| `integration-gpu`(pull) / `integration-verify` | 878 / 818 零 `Fatal{` | -| 79 例 retrace(`MOBILEGL_PIPE_VERIFY=1`) | 79/79,79/79 armed,零 `Fatal{` | -| 两个阴性对照 | 篡改字段变红、抽掉一个填充戳记在那条 verb 上变红 | -| 测试名 | 0 删除,+29 | - -verify 构建的代价:`integration-verify` 与 `integration-gpu` 同量级;79 例 retrace 在 4 路下约 20 分钟。 +全文 §2.1–§2.3:[`notes/p1/README.md`](notes/p1/README.md)。 ## 3. P2(`738b289d`) -### 3.1 门 +- 门:G1 4 处认定 resize、`.text` +160 B;单元 1566×3;`integration-gpu` 916 三臂;retrace push / verify 79/79。 +- DriverBench(桌面):边界 T1 = Espryt +322 / Magma +345 ns/draw;P2 自己的 tracker + CSO 比它替掉的 P1 残余填充便宜(−228 / −354 ns)。 +- 设备 A/B 的首轮是 -O0 构建(勘误,不作基准);Release 基准见 §4.4。 -| 门 | 结果 | -|---|---| -| G1 pull 构建符号 | 0 增 / 0 删 / 0 重命名;**4 处认定 resize**(`RenderState::{RenderState, SetCapability, IsCapabilityEnabled}`、`_GLOBAL__sub_I_DirectGLES.cpp`);`.text` +160 B | -| G5 `SyncRenderState` | `RenderStateImpl` 段 sha 逐字节相同 | -| G2 / G14 | 名差 0;0 删除 / +119 | -| 单元 | 1566 × {pull, push, verify} | -| `integration-gpu` | 916/916 × {pull, push, `MOBILEGL_PIPE_PUSH=0`};渲染状态敏感子集 72/72 | -| `integration-verify` | 828 零 `Fatal{` | -| 79 例 retrace | push 79/79;verify 79/79 armed 零分歧 | -| G7 setter 一致性阴性对照 | 变红并点名 `SetColorMask` | -| `CsoContentAddressing` / verify 三组对照 | 6/6 / 44/44(`PoisonOmitted`、`VerifyCorrupted`、`HandleRecycle`) | +全文 §3.1–§3.5:[`notes/p2/README.md`](notes/p2/README.md)。 -### 3.2 设备配对 A/B(-O0 勘误) +## 4. P3a(`fde5fda3`) -两机 64 次运行(小米 + Oppo,四 trace × 两后端 × finish 开/关,reboot-clean、钉频、best-of-3、尾 200 帧)所用的两臂 APK 是**未优化构建**(`libMobileGL.so` 43.2 MB / `.text` 21.8 MB,对 Release 15.5 / 9.4 MB),绝对值与差值都是 -O0 读数,不作基准线;Release 基准线以 §4.4 为准。-O0 读数:小米 p50 **+8–14%**(四 trace 两后端),Oppo Espryt **+16–18%** / Magma **+10–11%**,p99 同向;finish 开关两臂几乎一致(多出的是客户端 CPU);计数器两机逐字相同。原始 32 行表在 git 历史(`ea34bccb` 版本的本文件 §10)。harness 误杀记录:`trace-replay-ci.sh` 对 `pidof` 单次采样失败即 force-stop,`dev` 侧跟进。 +- 五部分门全绿:G1 0/0/0/0、`.text +0`;G5 十一函数逐字节相同;单元 1619×3;`integration-gpu` 958 五臂。 +- **Release 两机配对 A/B**:P2 边界 +6–12%(两机两后端一致);P3a 在 26.3 / sodium 上几乎不再加价,**在 rd12 上把差距推到 +27–30%**(VAO 切换密)。MC 26.3 在 Adreno 上 p99 +3.4%。 +- DriverBench:P3a 自己每 draw Espryt +699 / Magma +422 ns。 -### 3.3 DriverBench:T1 / T2(桌面 llvmpipe / lavapipe,`wsl_p2_bench.sh`,Release,240 帧 × 5 次取中位数,`ns_per_op`) +全文 §4.1–§4.5:[`notes/p3a/README.md`](notes/p3a/README.md)。 -| 臂 | `mc_vanilla_draw`(ns/draw) | `mc_state_toggle`(ns/开关对) | `mc_pass_switch`(ns/pass) | +## 5. P4a(`8c458cd5`) + +- 全门:G1 0/0/0/0;G5 17 区;单元 1785×3;`integration-gpu` 1117 七臂;verify 920;八族拒绝普查 0。 +- **Redmi 三臂 A/B**:P4a 自己在 Espryt 上 +3.4 – +5.7 pt(0.05–0.38 ms/帧),Magma 在噪声内(消费者门让它一条不发);MC 26.3 p99 +6.1%;上传形状 pull 与 push 逐项相同。 +- 桌面 DriverBench 分辨不出 P4a 这一档;可引用的是总边界 ≈ +1.1 µs/draw(Espryt)。 + +全文 §5.1–§5.4:[`notes/p4a/README.md`](notes/p4a/README.md)。 + +## 6. P5(`e61d0012` → `37fc4fdb`) + +- joint:OpenRA inproc 2/2 SSIM 1.0;`integration-split` 21/21;E1 barrier 阴性对照 14/14 全红;E2 丢 758 条 `DrawVbo` 后 SSIM 0.000036(丢 clear 被 overdraw 掩盖,不能作控制)。 +- 普查:inproc 1149 项中 27 个 wrong-answer(后续去向见 §7.2 末:2026-09-22 重跑时 24 绿、3 条变为具名停止)。 +- `maxrec` = 784 B(默认 cap 4 MiB 的 0.019%);ledger SEG_CMD 8 MiB / SEG_STAGE 32 MiB / SEG_REPLY 16 MiB / SEG_EVENT 256 KiB。 +- Redmi 四臂:split 臂全部停在索引 draw,barrier tax 未测。 + +全文 §6.1–§6.5:[`notes/p5/README.md`](notes/p5/README.md)。 + +## 7. P5b(主机门 `348d22a4`,设备 `82683d4a`) + +- 主机:`integration-split` 107/107;合并 inproc 普查 1267 项 811 / 203 / 62 / 191,旧 passes 零回退;79 trace 72 / 6 / 1。同名重跑 @ `3c80cd62`(2026-09-22):27 个 wrong-answer → 24 绿 / 3 具名停止;1267 项 → 1051 / 172 / 37 / 7(非绿全是钉了非默认 `MOBILEGL_PIPE_PUSH` 的对照臂);trace 77 / 0 / 0。 +- **Redmi**:正确性 8/8;**barrier tax**(split − push 逐线程 CPU p50)+5.9% – +18.2%。 + +全文 §7.1–§7.4:[`notes/p5b/README.md`](notes/p5b/README.md)。 + +## 8. P5c(`11ac3de6..b88e8487`) + +- 守卫开启下 unit 2187/2187、`integration-split` 111/111,每层守卫 red-once;纹理 `0xDD` audit 在两条 in-world trace 零 Fatal。 +- `rsp` 按帧:bsl 948.5/帧、complementary 1591.9/帧,残留全为对象类 15 行(值类 0)。 +- G1:3 个认定 resize、`.text` −16 B。Redmi 四臂复测未做。 + +全文:[`notes/p5c/README.md`](notes/p5c/README.md)。 + +## 9. P5d(Redmi,FCL + Minecraft 26.3,VD12,DirectGLES) + +| 臂 | fps p50 | client ms/帧 | apply ms/帧 | |---|---|---|---| -| native | 4771 | 22968 | 437759 | -| Espryt pull / push / push `PIPE_PUSH=0` / push 位 63 | 5121 / 5443 / 5671 / 5374 | 23753 / 24869 / 24584 / 24630 | 443300 / 446017 / 443807 / 435454 | -| Magma pull / push / push `PIPE_PUSH=0` / push 位 63 | 16900 / 17245 / 17599 / 17444 | 32705 / 33857 / 34082 / 34780 | 438406 / 446067 / 445006 / 444758 | +| inproc 起点(`56a77348`) | 64.3 | 15.03 | 12.42 | +| inproc 三轮(`1f8de61b`) | 103-106 | 9.2 | 7.0-7.4 | +| monolith | 115(120 Hz 封顶)/ 206(未封顶一次) | 5.0-6.2 | — | -| `mc_vanilla_draw`,ns/draw | Espryt | Magma | -|---|---|---| -| **T1** = push − pull(整个边界) | **+322**(+6.3%) | **+345**(+2.0%) | -| **T2** = push(`PIPE_PUSH=0`) − pull(P1 残余填充) | +550 | +699 | -| **T1 − T2**(P2 自己加的减的) | **−228** | **−354** | -| 位 63 − push(CSO 内容寻址净值) | −69(≈ 0) | +200(内容寻址每 draw 省 200) | -| blend-toggle / pass switch | +4.7% / +0.6% | +3.5% / +1.7% | +全文:[`notes/p5d/README.md`](notes/p5d/README.md)(报告 [`notes/p5d/P5D-INPROC-PERFORMANCE.md`](notes/p5d/P5D-INPROC-PERFORMANCE.md))。 -读法:P2 的 tracker + CSO 比它替掉的 P1 残余填充便宜;剩下的 T1 是尚未句柄化的填充与 dirty 走查。`mc_state_toggle` 由 `DriverBenchStateToggle` 钉住每帧 46 对开关;位 63 对照的开关 `CsoContentAddressingScenario` 常开(内容寻址臂 `csom ≤ 4` 且 `csom < csob`,位 63 臂 `csom == csob`)。 +## 10. P5e 设备验证一轮(`3cc4e1ec`) -### 3.4 计数器读数 +- `integration-gpu` 两条运行时臂从 1157/1294(137 SEGFAULT,当时没有任何门会看到)修到 1294/1294——此后它是常设步。 +- 设备:monolith 修复前 65 s 后进程消失,修复后两臂都存活进世界。 -`resid=` 已非零且棘轮压到底(1248 → 8):桌面 push retrace `iris-bsl`,`resid=197.07` B/帧 = 每 draw 0.64 块;`csom/csob` = 8 / 1415(CSO 复用比)。小米 push 臂(-O0,计数器与优化无关): +全文:[`notes/p5e/README.md`](notes/p5e/README.md)。 -| trace | Espryt `ers` / `etl` / `eub` | Magma `mfp` / `mpm` / `mdt` | `resid=` B/帧 | `csom` / `csob`(每 120 帧) | -|---|---|---|---|---| -| `minecraft-1.21.4-in-world` | 6020/1850 · 6884/986 · 6962/908 | 0/7278 · 6030/1248 · 5928/1350 | 178.31 | 2 / 1719 | -| `improved-transparency-minecraft-26.3` | 78960/1367 · 75378/4949 · 75199/5128 | 10740/68928 · 67660/1268 · 78800/868 | 185.36 | 0 / 1157 | -| `minecraft-1.21.4-fabric-iris-bsl-in-world` | 113/133 · 85/161 · 82/164 | 0/177 · 86/91 · 81/96 | 370.67 | 1 / 122 | -| `minecraft-1.21.4-startup` | 174/185 · 305/54 · 305/54 | 0/118 · 0/118 · 0/118 | 25.08 | 9 / 181 | +## 11. P5e run-ahead 武装后的设备矩阵(`25fba0d5`) -CSO 内容寻址在稳态窗口里几乎不再铸造;memo 门形状与 §1.3 的拉取基线一致。**未测**:逐 dirty 位触发率(`FireCount`/`WalkCount` 已实现但不在汇总行)、每 draw payload 直方图(只在 teardown JSON 里)。 +- VD12(~850 draws/帧,CPU 定频):run-ahead 相对 lockstep client −18%、apply −33%、fps +6%;monolith 与 run-ahead 都撞 120 Hz 上限,只有 lockstep 撞不到。 +- **VD32(~3550 draws/帧)**:monolith 58.5 / run-ahead **59.9** / lockstep 35.5 fps(中位数)——**inproc 与 monolith 齐平**;run-ahead 相对 lockstep p50 +69%、client CPU −40%。两侧不平衡(apply 11–12 ms vs client 15.7 ms)是下一个优化方向(开放问题 18)。 +- 未定频的逐线程 CPU 不能跨臂比较(walt governor 会把 policy0 从 2745 拉到 748 MHz)。 -### 3.5 遗留判定 +全文 §11、§11.1:[`notes/p5e/README.md`](notes/p5e/README.md)。 -- `MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1` 的 186 条过滤不进比对器(P3a 在 push 下跑了一次 958/958;进比对器推迟到 P3b)。 -- Track H 单位成本的日历口径未记录(产出侧在案:11 条 memo 删除 + 2 条重键零回归)。 -- 句柄 ABA 对照"重键前红":修复前 `HandleRecycle` 28 条中 1 条红(`AbaControl` 期望死对象的红却看到替换对象的绿);修后 32/32,且关掉 `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` 两臂都 `observed=FRESH` 并失败——污染只由被打掉的身份产生。 +## 12. P6 出口测量(2026-09-22,Redmi + WSL) -## 4. P3a(`fde5fda3`,基线 `5cb826b0`,44 个提交,37 文件 / +9273 / −230) +- **门 7 配对 A/B**(rd12 in-world,~1471 draws/帧,两会话各一次 reboot):inproc vs spawn 的 client CPU p50 **−0.15% / +0.16%**,在臂内散布内,判读 **tie**;monolith 约贵 30%。 +- **门 8①** 门铃:client 121.1 waits/帧两传输逐字相同;server park 率 0.042–0.06%;socket 阻塞读既不更便宜也不更贵。 +- **门 8②③**(inproc 桌面 retrace):`SEG_STAGE` 稳态中位数 OpenRA 130.8 KB/帧、rd12 816.2 KB/帧;分块后记录/帧 211 / 7 497;`maxrec=1808`,默认 8 MiB chunk 预算从未被打到。负控 `STAGE_MB=1` 下 `seg` 运行总量逐字节不变。 -### 4.1 五部分门(本地 `~/w7/pipe`) +全文 §12.1–§12.7:[`notes/p6/README.md`](notes/p6/README.md)。 -| 门 | 结果 | -|---|---| -| 构建 pull / push / verify | rc 0 / 0 / 0(fail-fast,绝不拿陈旧二进制下判决) | -| A 门 include 闭包 / C 门 `pGLContext` / G13 | 4 probes 0 problem / 空 / 空 | -| **G1** | `.text` 10806323 → 10806323(+0);27811 符号 **0 / 0 / 0 / 0** | -| **G5** 十一函数(pool / 延迟释放 / ring / `FlushPendingRangesNow` 与 `FlushPendingRangesFrom`) | 对 `44c2b5cf` 与 `5cb826b0` 都逐字节相同;self-test 两个阴性对照按名变红 | -| `integration-verify` / 79 retrace verify | 842/842 零 `Fatal{` / 79/79 armed 零分歧 | -| G2 / G14 | 差 0 / 0 删除 +58 | -| 单元 | 1619 × 3 | -| `integration-gpu` | 958/958 × {pull, push, `PIPE_PUSH=0`, `0x7f`(G12 子系统关闭臂), `ESPRYT_DISABLE_INVALIDATE_FLUSH=1`} | -| buffer/VAO 族 / `RenderStateSpans`+`Residual`+`VertexInputEmit`+`ResourceEmit` | 202/202 / 48/48 | -| `HandleRecycle`(verify) / 子系统对照 / verify 对照组 | 60/60 / 10/10 / 64/64 | -| G7 vertex-input 阴性对照 | 变红并点名 `IsBgra` | -| 79 retrace push | 79/79 | -| G3b 具名五条 | 桌面全绿(都在 79 例扫描内),但单独的具名扫描因门脚本把正则写成逗号清单而选中 0 例;设备侧按开放问题 17 排除 `create-indirect` → 记为"桌面全绿、设备侧部分" | -| `gen_pipe` / `gen_pipe_dirty_surface`(扫描根扩到 `MG_State/GLState`) | self-test 7 / 21 个阴性对照全部触发;75 个 mutator 全映射,0 COARSE / 0 UNDECIDED | +## 13. P3b/P4b wave 2-D 包 D2(`cf7ca59f`) -### 4.2 "重键前红"与验证轮找到的三个缝隙缺陷 +- 上传形状金标四臂(monolith / inproc / spawn / tcp)同为 `emit=9 box=9 rect=0 jobs=9`;零补丁 red-once:`MOBILEGL_ESPRYT_DISABLE_UNPACK_RING=1` 翻成 `box=3 rect=6 jobs=183`。 +- Mali 帧时增量不可调度(无 Mali 设备),按 Adreno 记录。 -buffer 类的红前证据(package C 尚未注册 `MGPipeResourceOps` 时):`HandleRecycle` 的 `.AbaControl` 臂通过(它断言的就是被污染的内容)、`.Legacy` 通过、`.Handles` 可见地 skip;C 注册后两条 skip 转为断言,最终 60/60。 +全文 §13.1–§13.3:[`notes/p34b/README.md`](notes/p34b/README.md)。 -| # | 缺陷 | 表现与修法 | -|---|---|---| -| F1 | vertex buffer 条目按属性的 GL 绑定点解析,而不是按属性下标 | 两者只在 `KHR-GL43.vertex_attrib_binding` 题材上分叉;`VertexAttribBindingScenario` 6 → 3 | -| F2 | ensure 路径读 `MGPResourceDesc::HasDefinedContent`(上一次 respecify 时的事实)判断影子有无字节 | `glBufferData(size, NULL)` + `glBufferSubData` 后应用字节被静默丢掉;改为持有前端对象时读 `HasDefinedContent()` | -| F3 | 惰性生成的 twin 从不发布影子基址,fp64 收窄拒绝每个 draw | `hostBytes` 终身 null → Adreno workaround 禁用属性;在算出基址处补发布,只对非采纳资源 | +## 14. P6.5 第一波(`fe28bdb5`) -方法学:包 worktree 里 `tools/trace_replay/fixtures` 是 LFS 指针,对着指针跑 retrace 报 `passed 2 / 79`、`ssim=None`——**假红**;base-instance 对照在 llvmpipe 上要先关掉原生 `GL_EXT_base_instance` 才可证伪。 +- 主机 unit 2399、integration-tcp loopback 102/102;**Redmi device-102 102/102,逐条 run-ahead ARMED**;代表性 retrace OpenRA 1.0、startup 0.999999511、in-world 0.999995438。 +- 真实远端 kill **133 ms**、真实 Wi-Fi 中断 **5053 ms** 闩住 device loss。G1 0/0/0/0。 +- 未测:39 例 device golden 矩阵、逐帧 `kWaitReply` / RTT、`PRESENT_CREDIT` 1/2/3、tcp-vs-spawn 逐线程 CPU。 -### 4.3 Track H 单位成本 +全文:[`notes/p65/README.md`](notes/p65/README.md)(`performance.md`、`validation-status.md`、`wire-and-validation.md`)。 -日历:四个包(contract/wire、client、espryt、gates)各一轮实现 + 对抗性评审 + 至多两轮返工,全部在 2026-09-08 一天内,对 27 天绊线 1/27。memo 账:直接删除普查 11 条里的最后一条(`ConvertedVertexStreamKey::sourcePin`);句柄臂上退役 VAO twin 五个同步 memo 成员;重键 VAO twin 索引槽 memo、`ResolvedDrawBuffers`、twin 同步门、`ConvertedFloat64Stream`;**保留**(`MOBILEGL_PIPE_LEGACY_MEMOS` 下仍编译):以上全部成员、`g_pendingFetchBaseInstance` 族、D-K 那组——真删会移走 pull 符号或改 `sizeof`,G1 的 0/0/0/0 就是这条的度量,随 P13 退役。 +## 15. P7(2026-09-23) -### 4.4 两机 Release 配对 A/B、MC 26.3 的 p99、DriverBench(记录项) +- 门 3(终局窗口 3,`p7w8-78e71f2b`)真机 ssim **36/36**:33 例四臂逐位同,三例 Iris 按分布判据(ID-P7-62),OpenRA 四臂 1.000000。 +- 门 5 CTS 五块 AFTER:shader-image +4.35 pp、texture +0.96 pp、其余持平,0 新 crash。 +- Magma 两进程车道 split / spawn / tcp 102 / 81 / 71;M2 后主机 bsl spawn server 825 → 546 MiB、映射 41k → 5.8k。 -协议:reboot-clean、`pin_device.sh` 钉频并前后 `check`、两臂背靠背、`--benchmark-repeats 3 --benchmark-tail-frames 200`、best-of-3、`--benchmark-no-finish` 主臂;APK 都是 `3e298c9a` 的 Release trace 构建(`.text` 9.4 MB)。臂:pull = pull 库;P2 = push APK `MOBILEGL_PIPE_PUSH=0x7f`;P3a = push APK 默认 `0x1ff`。`create-indirect` 排除(开放问题 17)。单位 ms/帧。 +全文:[`notes/p7/README.md`](notes/p7/README.md)(证据 `notes/p7/device-window-1/`、`device-window-2/`)。 -| 设备 | trace | 后端 | pull p50 | P2 p50 | P3a p50 | pull p99 | P3a p99 | 备注 | -|---|---|---|---|---|---|---|---|---| -| 小米 | `improved-transparency-minecraft-26.3` | Espryt | 10.810 | 11.485(+6.2%) | 11.697(**+8.2%**) | 25.457 | 26.322 | | -| 小米 | 同上 | Magma | 10.675 | 11.473(+7.5%) | 11.459(**+7.3%**) | 25.014 | 26.035 | | -| 小米 | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 8.220 | 9.117(+10.9%) | 10.650(**+29.6%**) | 21.895 | 24.487 | | -| 小米 | 同上 | Magma | — | — | — | — | — | 三臂都 SIGABRT(`scudo::reportMapError`,`dev` 侧) | -| 小米 | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt | 1.292 | 1.349(+4.4%) | 1.382(**+7.0%**) | 2.418 | 2.443 | | -| 小米 | 同上 | Magma | 0.485 | 0.504 | 1.010 | 1.469 | 2.187 | 亚毫秒帧,噪声 | -| 小米 | `create-instancing` | Espryt / Magma | 944.4 / 1003.9 | 945.2 / 1019.5 | 949.9 / 1015.6 | — | — | 两帧 fixture | -| Oppo | `improved-transparency-minecraft-26.3` | Espryt | 9.741 | 10.570(+8.5%) | 10.542(**+8.2%**) | 26.312 | 27.022 | | -| Oppo | 同上 | Magma | 8.162 | 8.920(+9.3%) | 8.973(**+9.9%**) | 22.851 | 23.638 | | -| Oppo | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 9.938 | 11.009(+10.8%) | 12.963(**+30.4%**) | 27.137 | 29.765 | | -| Oppo | 同上 | Magma | 8.001 | 8.933(+11.6%) | 10.161(**+27.0%**) | 24.723 | 27.355 | | -| Oppo | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt / Magma | 1.896 / 0.399 | 1.770 / 0.431 | 1.841 / 0.437 | 2.993 / 1.206 | 3.079 / 1.409 | 噪声内 | -| Oppo | `create-instancing` | Espryt / Magma | 1075.1 / 758.3 | 1045.1 / 731.0 | 1060.1 / 760.0 | — | — | 两帧 fixture | +## 16. P12 子集(2026-09-24,未收官) -读法:(1) **P2 边界在 Release 下的真实代价是 +6–12%**,两机两后端一致;(2) P3a 在 26.3 与 sodium 上几乎不再加价,**但在 rd12 上把差距从 +11% 推到 +27–30%**——rd12 每帧 VAO/buffer 绑定切换远多于 26.3,每次切换走一遍 `set_vertex_buffers` 构造 + `ContentHash` + applier 记录 + 逐属性走查(P4a 补的 `csob-blob` 计数器给了字节口径:rd12 1.06 MB/帧,其余用例中位数 2.9 KB/帧;DirectVulkan 恒 0);(3) **MC 26.3 在 Adreno 上的 p99**:25.46 → 26.32 ms(+3.4%),仍在采纳基线的 21–26 ms 档;(4) `mpr` 在设备上成立(26.3 两机都是 8,P2 臂恒 0);(5) 计数器三臂逐字相同。 +- 真机七项检查(审查轮之前的 `29b7b284`):Espryt / Magma 上屏 OpenRA ssim 1.000000,同进程第二会话 0.999999511;失窗后新会话 1.0;离屏 service 1.0。审查轮之后未复测。 +- 主机门(审查轮)unit 2605/2605;G1 同机前后 `.text` 一致、符号 0/0。 -小米 rd12 + Magma 三臂(含 pull)都 `SIGABRT`:`scudo::reportMapError` ← `scudo_calloc`,pull 库与 P3a 前的 Magma 路径符号一致——既有 bug,`dev` 侧任务。 - -DriverBench(`wsl_p3a_bench.sh`,`c20e2f2b`,`mc_vanilla_draw` ns/draw;T2 = `0x7f` 臂): - -| | Espryt | Magma | -|---|---|---| -| **T1** = push(`0x1ff`) − pull | **+1048**(+20.5%) | **+611**(+3.6%) | -| **T2** = push(`0x7f`) − pull | +349 | +189 | -| **T1 − T2 = P3a 自己加的** | **+699** | **+422** | -| 位 63 − push | −6 | +20 | -| blend-toggle / pass switch | +5.2% / ≈ 0 | +2.8% / ≈ 0 | - -读法:P3a 的 buffer/VAO 句柄路径每 draw 多花 Espryt 699 / Magma 422 ns(Magma 没有句柄化的 VAO 消费者,全是 client 侧发射 + applier 记录);进优化清单:vertex-input 发射的 per-draw 抑制与 applier 记录就地更新。`DriverBench` 必须经 `DRIVERBENCH_EGL_LIB` 显式 `dlopen` provider,不得靠 `LD_LIBRARY_PATH`(glvnd 会把 GPU 悄悄换成 llvmpipe)。 - -### 4.5 决定与遗留 - -- **ID-15**:`Managers.cpp` 的三层 flush 排水梯每个预处理臂各一份——push 构建跑 `FlushPendingRangesFrom`,pull 构建跑逐字节不变的 `FlushPendingRangesNow`;两个名字都是 G5 行,各对自己的 pin 比。转发函数不可行(G5 提取器不认预处理)。 -- **M-3**:fp64 顶点数组收窄在句柄臂上少做一处 `SyncGpuWrites`(`SyncFloat64AttributeAsFloat32ByHandle` 手里只有句柄)——默认掩码下两臂唯一的行为差异(fp64 数组 + shader 写过的源 buffer + 无显式回读),P8 把拉取搬到 client 侧即关闭。 -- 整体 diff 终审抓出两个跨包接缝:client 为每个 VAO 铸的 `VertexElementsCso` 槽在 Magma 下泄漏(修为 `~VertexArrayObject` 走后端无关的死亡路径);`FlushPendingRangesFrom` 在 G5 覆盖之外(→ ID-15)。 -- CI 独有的 teardown 崩溃(`exit()` 时静态析构顺序 UAF):`MGPipeSlots()` 等单例与四个持有前端 `SharedPtr` 的静态 `PipeInputs` 改为退出时泄漏(`d54ec57a`、`6515c8e6`、`fde5fda3`),ASan 10/10 干净;复现钥匙 `GLIBC_TUNABLES=glibc.malloc.tcache_count=0`。 -- `dev` 侧跟进(拆分不顺手修):`g_uploadRing` 不被 `OnBackendContextDestroyed` 重置(良性,`RingAvailable` 自愈);`ScopedDefaultUnpackState::s_synced` 没有失效路径。 -- `MG_Test/Buffer/BufferTest.cpp` 的 fixture 只 scope 了 `BufferBackendOps` 不 scope `MGPipeResourceOps`,26 条被路由进 pipe——修后 86/86;给它一个 pipe 形 mock 是跟进项。 -- 峰值 RSS(push vs pull,79 例):`retrace_gate.py` 不报,没有数。 - -## 5. P4a(`8c458cd5`,基线 `37da3c3a`,101 个提交,86 文件 / +30066 / −384) - -### 5.1 全门(`6035c9d7` 全量 + `8c458cd5` 复跑) - -| 门 | `6035c9d7` | `8c458cd5` | -|---|---|---| -| **G1**(认定 resize 集为空) | 0 / 0 / 0 / 0,`.text` 不变 | 同上 | -| **G5** | P3a 十一函数 rc 0;P4a **17 区 / 3 文件** rc 0,self-test 8 个阴性对照按名变红 | 同上 | -| G2 / G14 | 差 0 / +275 | 差 0(2902 条)/ +314 | -| 单元 | 1772 × 3 | **1785 × 3** | -| `integration-gpu` | 1091/1091 × 七臂 | **1117/1117 × 七臂**(默认 `0x1fff`、`0x1ff`、`0`、`0x9ff`、`0x5ff`、pull、`ESPRYT_DISABLE_INVALIDATE_FLUSH=1`;DirectVulkan 559/559) | -| `integration-verify` | 896/896 零 `Fatal{` | **920/920** | -| retrace(79 例) | verify 79/79 armed 零分歧;push 79/79;G3b 12/12 | push 79/79 | -| 阴性对照脚本 | `g7_negative_control.sh`、`p3a_vertex_input_negative_control.sh`(`IsBgra`)、**`p4a_descriptor_negative_control.sh`(`Layered` 与 `borderColorForm`)** rc 0 | 同上 | -| 子系统对照套件 | `CsoContentAddressing` + `ResourceSubsystemControl` + `ObjectSubsystemControl` 24/24;`0x9ff` 依赖拒绝臂 14/14;`HandleRecycle`(verify)180/180 | 同上 + 184/184 controls | -| 八族拒绝普查 | — | **0**(逐用例读私有日志;`ctest -V` 是假零) | - -G9 的"落地前必须红"没能通过公共 GL 达成(回读模拟自己会设 `GL_DEPTH_STENCIL_TEXTURE_MODE`):改成 `PipeApplyPeek` 白盒断言,变异下 4/4 变红。真正红过再绿的两条来自终审:per-level respecify 丢上传(`0x1fff` 下读回全黑)与 delete-then-draw(`pure virtual method called`)。CTS `direct_state_access.framebuffers*` / `packed_pixels`(G15)未跑,随 P3b/P4b 补。 - -### 5.2 缝的分类(契约七次修正 `c0b`…`c0g` + 一轮缝类审计 + 一轮终审修复) - -| 类 | 实例 | 症状 | 预防 | -|---|---|---|---| -| 编码没定死 | `MGPSubData::Target`、`DepthStencilMode`、`MGPSurface::Kind`、缺 `TextureTarget` | 两侧各自发明一套;`Texture1D == 0` 与 `kMGPipeResourceTargetBuffer == 0` 撞上 | 编码表放进契约 | -| 身份 vs 内容 | 内置 sampler:client 按身份铸、cache 按内容铸 | 查找永远落空 → 17 条 Iris trace 用驱动默认采样器 | 每 kind 两侧 handle 规则表 | -| 记录键错了维度 | framebuffer 记录按"当前绑定"存,DSA 按名字来 | 打进从没收到附件的 FBO | 记录按对象存;`Named = 3` | -| 进程级单例 vs 每上下文命名 | `CompositeResolver` 按管线 GL 名记忆 | 释放另一个上下文还活着的合成体 | 单例的键含上下文身份 | -| 破坏性客户端动作缺前置条件 | 按 acceptance 清 dirty,但 Magma 无消费者、依赖位只在服务端拒 | 上传丢失:66 条 DirectVulkan 用例、`0x7ff` 下 438/491 | 消费者门 + 依赖门都在客户端:一条不发 | -| 快门看不见自己的主体 | `glBindSampler`、SSO 下 `GetCurrentProgram()`、`glBindImageTexture` 只换 level | 记录停在上一次的值(`create-indirect` SSIM 0.887) | "记录字段 → setter → 快门"完备表;优先混入已有世代 | -| 清得太宽 | `resource_respecify` 清掉整张待上传表 | 已接受的那一级永久丢失 | 作用域随调用走 | -| 死亡没通知发射方 | 六个死亡 helper 只释放 slot | 已删句柄解析到已释放对象 → UAF | 死亡转发给每个 emitter | -| 门不能变红 | G7 脚本永远编译不过、`HighWater(ShaderCso)` 取段顶、G9 红前态公共 GL 不可见 | 绿得毫无意义 | 每个门带阴性对照并真跑过一次红 | - -方法论结论:六个包的 v1 全部通过自己的门、六份对抗性复审全部判 REWORK,最贵的两个缺陷(丢上传、delete 后 UAF)是**整体 diff 终审**才抓到的——它们跨包,各方自洽。终审是唯一能看见跨包契约的那一轮。 - -### 5.3 Redmi 三臂 A/B、MC 26.3 的 p99、上传形状(记录项) - -设备换为 Redmi `2f7cbe2e`(与小米同 SoC 同定频点,数值可比;GPU 当时钉 1050 MHz;主动风扇 level 2,40 个样本 40 个 PINNED)。APK `8c458cd5` Release 双臂;`--benchmark-no-finish`,尾 200 帧、best-of-3、逐线程 CPU p50 ms;`0x1ff` = P2+P3a 边界,`0x1fff` = P4a 默认。 - -| 用例 | 后端 | pull | `0x1ff` | `0x1fff` | Δ P2+P3a | Δ 合计 | **P4a 自己** | -|---|---|---|---|---|---|---|---| -| improved-transparency-26.3 | Espryt | 10.716 | 11.754 | 12.124 | +9.7% | +13.1% | **+3.4 pt / +0.37 ms** | -| improved-transparency-26.3 | Magma | 10.603 | 11.543 | 11.585 | +8.9% | +9.3% | +0.4 pt(噪声) | -| rd12-odinlite | Espryt | 8.210 | 10.798 | 11.182 | +31.5% | +36.2% | **+4.7 pt / +0.38 ms** | -| rd12-odinlite | Magma | — | — | — | — | — | 三臂全 `rc=1`(`scudo`,`dev` 侧,换机仍复现) | -| fabric-sodium | Espryt | 1.312 | 1.406 | 1.454 | +7.2% | +10.8% | +3.6 pt / +0.05 ms | -| fabric-sodium | Magma | 0.474 | 0.502 | 0.507 | +5.9% | +7.0% | +1.1 pt(噪声) | -| 1.21.4-in-world | Espryt | 2.369 | 2.732 | 2.867 | +15.3% | +21.0% | **+5.7 pt / +0.14 ms** | -| 1.21.4-in-world | Magma | 1.028 | 1.147 | 1.145 | +11.6% | +11.4% | −0.2 pt(噪声) | -| fabric-iris-bsl | Espryt | 1.727 | 1.740 | 1.811 | +0.8% | +4.9% | +4.1 pt / +0.08 ms | -| fabric-iris-bsl | Magma | 0.742 | 0.788 | 0.786 | +6.2% | +5.9% | −0.3 pt(噪声) | - -读法:P4a 自己在 Espryt 上是 +3.4 – +5.7 pt(0.05–0.38 ms/帧),大头仍是 P2+P3a 的边界;Magma 两臂在四个用例上落在噪声内——消费者门在设备上的读数(Magma 一条 P4a 记录都不发);`vanilla` 是 P4a 占比最高的用例(draw 少、状态切换密)。**MC 26.3 在 Adreno 上的 p99**:pull 25.297 → P4a 26.841 ms(+6.1%),仍在 21–26 ms 档。**上传形状 pull 与 push 逐项相同**(79 例两臂 `tex[emit/box/rect/jobs]`:18451/16060/2391/39926 对 18453/16062/2391/39928,唯一差异是 2 次 `trp` 带来的重放上传)。线索:设备上 26.3 Espryt 的 `sve` ≈ draw 数(9143 / 9138),桌面 ~0.07/draw——每 draw 重发一次 sampler-view 集合,进 P3b/P4b 优化清单。`acc/draw` 在这一波普遍下降而 CPU 上升,不是矛盾而是该计数器的定义。 - -### 5.4 DriverBench(`wsl_p4a_bench.sh`,`8c458cd5`,`mc_vanilla_draw` ns/draw) - -Espryt T1(`0x1fff` − pull)= +1076.1,T2(`0x1ff` − pull)= +1064.7,T1 − T2 = +11.4;Magma T1 = +629.0、T2 = +452.6、T1 − T2 = +176.4。两臂绝对值在两轮之间各漂 ~200 ns 而差只有 10–130 ns,**桌面 bench 分辨不出 P4a 这一档**;可引用的是 T1 ≈ +1.1 µs/draw 的总边界(Espryt)与设备侧三臂表。Magma 的 +176 ns 不是"Magma 在跑 P4a"(消费者门让它一条不发),是 tracker 多算的快门加噪声。 - -## 6. P5(joint `e61d0012` → 落地 `eec0e836` → 收尾头 `37fc4fdb`) - -### 6.1 五部分门(joint)与落地快门 - -| 部分 | joint 实测 | -|---|---| -| 1 接口纯度 | include closure 4 probes / 0 problems;pull `MG_Remote` 符号 0、split 610;G1 `.text` 10806611 → 10806611、27814 符号 0/0/0/0;P3a/P4a G5 对 `ff2994d9` byte-identical | -| 5 覆盖 / 生成器 | `gen_pipe` self-test 9/9;dirty-surface 27/27(`UseProgram` 一项明确 UNDECIDED);field ownership 15/15;emitter/CSO 185/185;verify controls 4/4 | -| 3 行为 A/B | G2 差 0;G14 0 / +42;unit 1816×3、split 2038;Wire 58/58;`integration-gpu` pull 1128、push 1128、split-monolith 1149;split-inproc 普查 426 pass / 185 skip / 511 abort / 27 fail;`integration-split` 21/21(19 run、2 design-skip);persistent arm 2/2,split `pmap=2160.00, mpr=1`,push `pmap=0.00, mpr=1` | -| 2 语义 / retrace | `integration-verify` 930/930 零 `Fatal{`;OpenRA inproc 2/2 双后端 SSIM 1.0;push retrace 79/79;verify retrace 79/79 armed | -| 4 设备 | 未跑(joint 禁止 adb);见 §6.4 | - -落地快门(`eec0e836`,ID-66):split unit 2038;`integration-split` 22/22;push `integration-gpu` 1128;G1 0/0/0/0 `.text +0`。 - -### 6.2 退出门 E1–E6 与 27 个 wrong-answer - -| 门 | 实测与"为什么会红" | -|---|---| -| E1 barrier | reduced path 19 pass / 2 skip / 0 fail;`MOBILEGL_IPC_VERB_BARRIER=0` 选中的 14/14 全 abort,每个私有日志有自己的 `Fatal{BarrierViolation, ""}` | -| E2 OpenRA | 2/2 SSIM 1.0;x2:丢 29 clears 仍 SSIM 1.0(被后续地形 draw 覆盖),改为丢 758 `DrawVbo` 后 SSIM 0.000036;r2 验证真实 CTest 顺序 baseline → pull-library control → 恢复原库 → draw-drop,并断言库身份 | -| E3 persistent map | (a) 收尾头 6 selected 中 4 pixel red、2 条 `TheMapLandsInTheArmItsLaneDeclares` 设计性 skip 被 j0 判红 → r2 精选默认 / SmallRing 下 4 条 pixel case 各带自己的 assertion + 私有诊断;(b) ID-42 emulated membership green → 3 red → green;(e) x2 证明 wrap,r2 证明真实 retirement wait(1 MiB 臂 `ringwraps=1 ringwaits=1`,8 MiB 对照同时红) | -| E4 field ownership | 生成器 15/15;strict lane 19 abort / 2 skip,首条 `Fatal{UnmigratedPipeInput, "GetTextureContextId@Clear"}`——BARRIER-PULLED 债务的响亮读法 | -| E5 honest inproc | 19 pass / 2 skip;v1-r3 的 `StagedShadowProductionTest` 证明 ensure 上传 server shadow(删保护会读 client A 变红),覆盖 unmapped 对象;coherent-map 的 server 侧绕过由 r1 #1 关闭 | -| E6 phase gate | 普查在 v1-r3 修掉六条过宽 whole-store 拒绝后 **432 / 185 / 505 UnmigratedVerb abort / 27 failed / 0 segfault**(1149 项,`~/w7/p5b-c0b-census-logs/results.json`) | - -| 27 个 wrong-answer 家族 | 条 | 去向 | -|---|---:|---| -| `LayeredAttachmentShapeScenario` | 14 | P4b/P7 layered texture readback | -| packed depth/stencil `GetTexImage` | 3 | P4b/P7 texture-shadow readback | -| framebuffer `HandleRecycleScenario` | 5 | P4b/P7 readback(不是已证明的 handle recycle bug) | -| `PrimitivesGeneratedNoXfbScenario` | 3 | P7 query | -| `TextureParamsWithoutASamplerView` | 1 | P6 inspection forwarder | -| `P4aFinalFixScenario` FBO/RBO delete | 1 | 读回用 ReadPixels,完整像素因果未隔离;client-thread framebuffer death 归 r1 #2 | - -> 上表是 P5 的历史去向。**头上读数(`3c80cd62`,P7 wave 3 同名重跑)见 §7.2 末**:22 条 readback → 19 绿 + 3 条由错答变为具名停止(都是 `MOBILEGL_PIPE_PUSH=0` 的对照臂);3 条 query 全绿;inspection 与 FBO/RBO 各 1 条绿。 - -### 6.3 R-10、逐帧 ledger 与内存 - -规范 ledger(`ProtocolSmokeTest` 钉住):**SEG_CMD 8 MiB / SEG_STAGE 32 MiB / SEG_REPLY 16 MiB / SEG_EVENT 256 KiB**。`MOBILEGL_PIPE_STATS_PERIOD=1` 下 persistent-map 场景 `pmap=600.00 B/帧, mpr=1, rsp=35`(`rsp` 只是有 stamp 读点的下界)。inproc 进程峰值 RSS:server accept 时 11.5 MB、client teardown 141 MB(两角色共享一个进程,不是两个独立峰值)。 - -max record bytes(x2,`37fc4fdb`):Triangle / persistent map / OpenRA 25 帧 / SmallRing 都是 **`maxrec=784 B` / `SetVertexAttribDefaults`**,默认 cap 4 MiB(占 0.019%)——这些负载不需要 chunking;oversized `DrawVbo` 尾的 unit 具名拒绝 `Fatal{RingOverrun, "DrawVbo"}`。 - -**内容分块落地(2026-09-20,`1e7c372e` buffer / `9469d48e` texture)**:`SEG_STAGE` 仍是"一条记录一个整 blob"的 arena,但会超出它的内容由发射侧按 stage chunk 预算 `MGPipeStageChunkBytes()` = `clamp(segment/4, 4096, segment)`(默认 32 MiB → 8 MiB)切成多条 `resource_subdata`——buffer 范围走查与纹理一级的整宽 slab(服务端 `StagedTextureStore::AdoptRun` 拼回整级)。§7.2 普查为容纳单次 128 MiB 上传而显式设的 `MOBILEGL_IPC_STAGE_MB=256` 因此不再是这两条路径的必需;未接入分片的 record 类型仍由 `Fatal{RingOverrun, "SEG_STAGE"}` 具名拒绝。上段的 `maxrec` 数字是分块前的测量,当时分块后的逐 blob 字节分布尚无新测量;该分布与分块后的 `maxrec` 已在 §12.4 补测——结论是默认 8 MiB chunk 预算在已测的两条负载上从未被打到,`maxrec` 仍是 1808。 - -### 6.4 Redmi 四臂 A/B(split 未测) - -会话 2026-09-16,runner `eec0e836`,三份 APK 源码头 joint `e61d0012`;臂 = pull APK / push APK / split APK + `MOBILEGL_TRANSPORT=inproc` / splitctl(split APK 不设 transport)。40 组前后 pin check 全 P/P,37.2–39.9 °C。**27 组完成 / 13 组失败:全部 10 组 split 在 benchmark 前中止**(improved-transparency 双后端 `DrawElementsInstancedBaseVertex`,其余 `DrawElements`),另 3 组是 rd12/DirectVulkan 的既有 `scudo` 崩溃。帧 p50 / p99(ms): - -| case / backend | pull | push | splitctl | -|---|---:|---:|---:| -| improved-transparency / DirectGLES | 10.535 / 25.415 | 12.106 / 26.973 | 12.235 / 27.131 | -| improved-transparency / DirectVulkan | 10.742 / 25.031 | 11.678 / 26.134 | 11.837 / 26.476 | -| fabric-sodium / DirectGLES | 8.314 / 9.573 | 8.311 / 9.701 | 8.303 / 9.486 | -| vanilla 1.21.4 / DirectVulkan | 1.043 / 2.293 | 1.159 / 2.404 | 1.179 / 2.426 | -| rd12 / DirectGLES | 7.999 / 22.089 | 11.105 / 24.865 | 11.261 / 24.771 | - -splitctl 相对 push 的帧 p50 −0.1% – +1.6%(split build 的 monolith 臂与 push 同源等价)。barrier tax 在 P5 未测得,归 P5b(§7.4)。 - -### 6.5 收尾头停止点、r1 / r2 - -`~/w7/p5-final-gate.log`(`37fc4fdb`):构建 4 × rc 0;Part 1 G1 27,814 符号 0/0/0/0、`.text +0`、两条 G5 byte-identical;Part 5 完成;Part 3 unit 1817×3 / split 2086、Wire 58、pull/push GPU 1128、split-monolith 1149 零失败、reduced 22 零失败、broad lane 1149 selected(532 non-success,含 abort);E1 14 red;**E3(a) 因 2 条设计性 skip 被 j0-v3 的逐选项拒绝判失败,Part 2 / 4 未到达**。j0 的 skip 拒绝是对的(不能放宽),选择器才是错的。 - -收官审查(`p5-close-codex-review.md`,只读):1 blocker / 10 major / 2 minor(ID-73)。r1(核心四项):apply-role 不得进入 persistent-map producer;framebuffer death 转 apply mailbox;`PACK_SWAP_BYTES` 的 component / packed-word swap;command-ring retirement 等待与重试。r2(`37fc4fdb..d50183cb`,CI / 控制 / 跑器九项):broad debt lane 记录后继续硬门、全 skip 则 baseline 失败;E2 库身份 SHA256 恢复;E3(a) 精选 4 pixel case;`ringwaits` 只计真正阻塞的分配;split unit 强制 `MOBILEGL_ITEST_REQUIRE_GPU=1`;普查跑器零执行拒绝、launch 前清私有日志;G5 pin 自测驱动生产选择路径;c1f 改用 ID-67 用例。r2 包门:split unit 2087、reduced 22、push 1128 零失败;E1 14 own reds、E3(a) 4 own reds;smoke 16 core + 12 private;census 432/185/505/27。r2 没有新跑 pull G1(不影响 pull production code)。 - -过程记录(不计入实现产量):wave-1 跨族复审十项全部经独立 perturbation 确认(ring 空 wrap 算术、reply 几何差 16 B、blob 校验接受任意段、pad-bit 控制没发送该 bit、三个 CI 对照接受任意非零退出……);后续 v1 / c1 各轮的 review 计数在 ID-52/58/62/64;本地 `rereview` 身份污染的 81 + 58 个提交经 parent/env rewrite 接回 GitHub `ff2994d9`(ID-40),此后每阶段首个 commit 前先跑 `git var GIT_COMMITTER_IDENT`。这些数字是流程在 P5 尾声改变(ID-66)的原因。 - -## 7. P5b(主机门 `348d22a4`,设备源头 `82683d4a`) - -### 7.1 迁移与包证据 - -`348d22a4` 含 d1 / i1 / t2 / f1、r1 / r2、五槽 sync、具名 blit 与 GLES mip storage;发射表 A=2 / B=54 / C=15(目录 76 条,槽数与 opcode 数不是同一统计量)。 - -| 包 | 已落地行为与证据边界 | -|---|---| -| d1 | 19 个索引 / 实例 / multi-draw / indirect 槽经 `draw_vbo` 过线;独立包普查 77 个 Minecraft 后端用例 28 passed、49 first blockers,全部越过原 draw 首阻塞(默认 stage 32 MiB) | -| i1 | 七个 image / compute / barrier / copy-image / storage-block 槽;原首阻塞 239 归零;CopyImage 的 client-shadow 镜像被跳过,`GetTexImage` 前端回退仍可能读旧 shadow | -| t2 | 六个 XFB / 曲面细分槽;原首阻塞实测 140(`BeginTransformFeedback` 95 + `PatchParameteri` 43 + `BindTransformFeedback` 2);包内基线 490 / 191 / 390 / 78,原 432 passes 全保留 | -| f1 | 11 个 clear / copy / mip 槽;原 34 个首阻塞归零;bound named-clear 接线,unbound 仍具名拒绝 | -| r1 / r2 | P5 收官 #1–13;r1 定向 10 passed / 0 skipped;client-only push suppression 两个像素控制变红 | -| 具名 blit | `BlitNamedFramebuffer` 经 scoped read/draw binding 发布,退出恢复公开绑定;两后端四个 split 像素 entry 通过 + 15 unit | -| GLES mip storage | server 只验证 applier descriptor 的 Levels/extent;三个 mip 像素控制 3/3;iris-BSL GLES 单例 SSIM 0.997496(默认 stage 32 MiB);registry 只 Find 不 mint(barrier 债);RGB 三通道 CPU fallback 保持 Fatal | -| sync | 五条现有 opcode 接线,wire 只传句柄,native fence 归 apply 线程;原 LOCAL guard 使 trace 的 `FenceSync` 没真正执行,所以 d1 旧普查里没有可扣除的 FenceSync 首阻塞数 | - -包报告:`~/w7/notes/p5b/p5b-results/{d1-codex-v1,i1-v1,t2-codex-v1,f1-v1,r1-codex-v1,r2-v1,blit-codex-v1,mip-codex-v1,sync-codex-v1}.md`。 - -### 7.2 主机收尾门与合并普查(`348d22a4`,`~/w7/p5b-final-host-348d22a4/`;同名重跑 @ `3c80cd62` 见本节末) - -| 车道 | Selected | Passed | Skipped | Failed | -|---|---:|---:|---:|---:| -| unit-linux / unit-push / unit-verify | 1817 × 3 | 1500 / 1782 / 1795 | 317 / 35 / 22 | 0 | -| unit-split | 2132 | 2122 | 10 | 0 | -| gpu-linux-monolith / gpu-push-monolith / gpu-split-monolith | 1148 / 1148 / 1178 | 895 / 953 / 953 | 253 / 195 / 225 | 0 | -| split(`integration-split`,inproc) | 107 | 105 | 2 | 0 | -| verify / audit | 950 / 10 | 804 / 9 | 146 / 1 | 0 | - -G1 27,814 符号 0/0/0/0、`.text` 10,806,611 → 10,806,611;G5 两族 + pin 自测;生成器 / 纯度检查;G2 / G14 rc=0;E1 / E3(a)(精选 4 case)rc=0;E2 OpenRA 2/2、draw-drop SSIM 0.000036 / 758 records、pull-library 拒绝与库 SHA 恢复一致;smoke 16 core + 12 private。retrace-push **79/79**;retrace-verify 在 WSL 重启前完成 4/79,其余 75 条以 SHA-256 钉住的同一 verify 库串行续跑,合并 **79/79**;对账无缺步、无非零退出(`reconciliation-20260916.md`),`complete=true`、exit 0。一次 shell 异常(追加 runner 行触发 `-test-dir: command not found`)保留在原始输出,不冒充测试失败。 - -**P5b 基线。** 合并 inproc 普查(`~/w7/p5b-final-census-348d22a4/`):integration lane 显式 32 MiB,**1267 selected = 811 passed / 203 skipped / 62 aborted / 191 failed**;旧 1149 名全部保留、新增 118;旧 432 passes 全保留、零回退;旧 505 abort → 265 passed / 18 skipped / 58 aborted / 164 failed;旧 27 failed → 1 passed / 26 failed。完整 trace 显式 256 MiB(容纳单次 128 MiB 上传,不是默认容量修复):**79 = 72 passed / 6 aborted / 1 failed**(主跑止于 73/79,三轮续跑补齐;`create-indirect` DirectVulkan 在 llvmpipe 上内存膨胀 >60 GiB RSS 被守护杀死,两次复现,记 failed)。7 条未过项首阻塞:`rd12` DirectGLES `Fatal{InitialBytesNotCarried,"resource_respecify"}`、DirectVulkan `Fatal{BarrierTimeout,"Present"}`;`iris-photon`、`iris-derivative`、`create-indirect` 三个 DirectGLES `Fatal{UnmigratedEmulation,"texture-remint-pull"}`;`iris-bsl-esc-menu-854` DirectGLES `InitialBytesNotCarried`;`create-indirect` DirectVulkan 内存守护。通过项含 `improved-transparency-minecraft-26.3` DirectGLES SSIM 1.0 / DirectVulkan 0.999914、`iris-iterationrp` DirectVulkan 0.995833、`iris-bsl-esc-menu-854` DirectVulkan 0.998402。逐 trace 见 `joint-codex-v1.md` 的 census 块与 `identity.json` / `counts.json` / `trace-transitions.json`。 - -**同名重跑 @ `3c80cd62`(P7 wave 3 包 E1,2026-09-22,WSL `~/w7/p7-census-e1`,lavapipe)。** 方法逐字沿用上段普查的 helper(`~/w7/p5b-final-census-348d22a4/artifacts/census-helper.py`):取 monolith 条目的命令与 ENVIRONMENT,`MOBILEGL_IPC_STAGE_MB=32` 与 `MOBILEGL_ITEST_REQUIRE_GPU=1` 作环境底、条目自己的 ENVIRONMENT 覆盖之,再强制 `MOBILEGL_TRANSPORT`、私有 `MOBILEGL_LOG_FILE_PATH`、tcache 关,状态映射相同;名单 = 上段 1267 个名字(头上 0 个缺失)。跑器 `~/w7/e1bin/{wa27,walane}.py`,证据 `~/w7/e1-census/{wa27,lane-inproc2}-3c80cd62/`;lane 普查单并发墙钟 426 s。 - -| 读数 | P5b `348d22a4` | 头 `3c80cd62` | delta | -|---|---|---|---| -| 27 个 wrong-answer 同名,inproc | 1 passed / 26 failed | **24 passed / 3 aborted / 0 failed** | 23 转绿;3 条由错答变为具名停止 | -| 同上,spawn(P5b 未测) | — | 24 passed / 3 aborted / 0 failed | 与 inproc 逐名相同 | -| 合并 lane 普查 1267 同名,inproc | 811 / 203 skipped / 62 aborted / 191 failed | **1051 / 172 / 37 / 7** | failed→passed 187、aborted→passed 62、skipped→passed 31;passed→非绿 40、failed→aborted 4 | -| trace(CI split 子集),inproc | 79 = 72 / 6 aborted / 1 failed(含 `rd12` × 2,256 MiB) | **77 = 77 / 0 / 0**(默认 32 MiB;spawn 同为 77 / 0 / 0) | `rd12` 已 `ci: false` 出分母;其余 7 格全活([`notes/p7/iris-census-3c80cd62.md`](notes/p7/iris-census-3c80cd62.md) §6) | - -27 条按 §6.2 的家族: - -| 家族 | 条 | 头(inproc = spawn) | 机制 / 读法 | -|---|---:|---|---| -| `LayeredAttachmentShapeScenario` | 14 | **14 绿** | 未二分到提交;DirectVulkan 的 7 条在 `integration-magma-full-{split,spawn}` 的同名注册条目上也绿 | -| packed depth/stencil `GetTexImage` | 3 | **3 绿** | DirectGLES `Split/Spawn.ForcedDs` 与 DirectVulkan `Split/Spawn.Full` 的注册条目同绿 | -| framebuffer `HandleRecycleScenario` | 5 | **2 绿**(DG `Handles`、DV `AbaControlHandles`)/ **3 具名停止** | 停止的 3 条都是 `integration-monolith-control` 里 `MOBILEGL_PIPE_PUSH=0` 的对照臂:DG `Legacy` → `Fatal{PipeLegacyMemosDisabled, "MOBILEGL_TRANSPORT is not monolith and kMGPipeSubsystemSamplers (bit 11) is clear …"}`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:4289` 经 `StopOnArmlessPipeSubsystem`,`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2915-2918`);DV `Legacy` / `AbaControl` → server `Fatal{UnmigratedVerb, "Magma:clear-framebuffer-record"}`(`MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:8008`:push 全关时 transport 下没有 framebuffer record),spawn 臂 client 另记 `Fatal{ReadbackDeclined, "ReadPixels"}`。旋钮矛盾的响亮停止,不是错答;push 打开的两条同名兄弟全绿 | -| `PrimitivesGeneratedNoXfbScenario` | 3 | **3 绿** | DV `Split/Spawn.Full` 的 `CountsADrawMadeWithNoCaptureSpan` 同绿(`TheRerouteIsActuallyArmed…` 在 full 车道因不钉旋钮而 skip,按设计) | -| `TextureParamsWithoutASamplerView` | 1 | **绿** | DG `Split` / `Spawn` 注册条目同绿——ROADMAP 债务表的「待核」关闭 | -| `P4aFinalFixScenario` FBO/RBO delete | 1 | **绿** | 与 P5b 同 | - -lane 的 44 条非绿(37 aborted + 7 failed)**全部是 `integration-monolith-control` 条目、且自己的 ENVIRONMENT 钉了非默认 `MOBILEGL_PIPE_PUSH`**(0 / 0x7f / 0x1ff / 0x5ff / 0x9ff / 0x1fff / 0x8000000000001fff)——普查方法把 monolith 对照臂也强制上了 transport,这 44 条读的是「这组旋钮在 split 下没有臂」:35 条具名停止(18 × `Magma:clear-framebuffer-record`、17 × `PipeLegacyMemosDisabled`),2 条 `Fatal{UnmigratedPipeInput, "GetBufferBindingPoint@DispatchCompute"}`(掩码 0x1fff),7 条断言失败在单体专用仪器上(4 × `CsoContentAddressingScenario` 在 client 日志里找带 `cso[csom= csob=]` 的统计窗口,而 split 下 CSO 在 server 铸造、那一行在 server 日志;3 × DV `HandleRecycle.AbaControlHandles` 的 buffer / VAO 用例是负控,期望 STALE,wire 路径读到 FRESH——即像素正确、负控不起作用)。这 44 条之外的 1223 条全部 passed 或 skipped。其中 40 条在 P5b 是 passed,头上转为上述读法;没有一条是默认旋钮下的产品回归。 - -### 7.3 唯一收官审查与定向修复 - -审查 `p5b-close-codex-review.md`(`348d22a4`,diff base `37fc4fdb`,只读):**0 blocker / 2 major / 1 minor**。 - -| 项 | 问题 | 处置 | -|---|---|---| -| Major 1 | user-index span 在段内但 Size 可能短于 Count × IndexSize | `a021e3cc`:encoder / decoder / sink 共用 shape/extent gate(单 range、宽度 1/2/4、`Uint64(Count) × IndexSize ≤ Size`),三端短 span 均拒绝、段末 exact-fit 通过;定向 9/9 | -| Major 2 | `ClientWaitSync` 保留 64-bit timeout 但 applied/reply 等待一律 30 s | `82683d4a`:预算 = ceil(timeout ns / 1e6) + 30,000 ms,有限 chunk;普通容量等待 / `FenceWaitServer` 仍 30 s,shutdown 可唤醒。**该 30 s 常量后来在 CI 修复轮改成 120 s(`68b55ea3`),原因见本表下注** | -| Minor 3 | ReadPixels exact-size reply 的未知 status 穿过 production helper | `82683d4a`:tight 与 bounce 路径拒绝 status=3,`Fatal{ReplyStatusInvalid}` | - -定向 `RemoteClientTest` 7/7(0 ns、1 ns、60 s、UINT64_MAX 预算,`FenceWaitServer`、shutdown、未知状态)。合并后 quickgate 里 split 单元车道仅有的 2 个失败(`PipeWireCodecTest.UserIndexSpan*`)是测试日志捕获缺陷(库按进程截断日志,fork 子进程增量读读空),`7cb29d46` 修复后 3/3。本阶段不再做第二轮全门或审查;原始全门源头仍为 `348d22a4`,三项修复只以定向证据补齐。 - -> 上表 Major 2 的 30 s 是 `82683d4a` 当时的值,保留为历史记录。**当前值是 120 s**(`68b55ea3`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`),因为 lavapipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,30 s 的看门狗会把合法慢路径间歇判红;详因见 `ARCHITECTURE.md` §17.1。`RemoteClientControls.FenceWaitBudgetHonorsGlTimeoutAndFiniteTransportChunks` 的期望值(30000/30001/90000)已同步改为 120000/120001/180000。 - -### 7.4 Redmi 出口与四臂 A/B(`82683d4a`,APK `p5bcodex2`) - -Redmi `2f7cbe2e`,证据根 `MobileGL/.trace-work/p5b-redmi/p5bcodex2/2f7cbe2e/`;全部组合显式 `MOBILEGL_IPC_STAGE_MB=256`。**钉频口径变更**:2026-09-11 的厂商 GPU 上限(1050 MHz)已消失,本次 deterministic pin 为 **1100 MHz**;与 1050 MHz 时代的活动不可比钟频,只有同场四臂配对可比。 - -**正确性 8/8**:`improved-transparency-minecraft-26.3`、`minecraft-1.21.4-in-world`(GLES SSIM 0.999995)、`minecraft-1.21.4-fabric-sodium-in-world`、`minecraft-1.21.4-fabric-iris-bsl-in-world` × 双后端,inproc split 臂,逐组钉频取证 + PNG / SSIM 阈值 + inproc + apply 线程 + 正 wire 记录三证,skip 即失败。**四条 A/B trace 首次在 Redmi 上以独立 apply 线程渲染——P5b 出口判据达成。** - -**四臂 A/B(barrier tax 首测)**:32 组中 24 组带 200 帧尾验证全绿;三次重复取 wall-time 均值最低者、尾 200 帧(`ab-tables.md`)。逐线程 CPU p50: - -| trace | 后端 | push−pull | **barrier tax(split−push)** | barrier tax 帧 p50 | -|---|---|---:|---:|---:| -| improved-transparency-26.3 | DirectGLES | +15.6% | **+10.3%** | +0.1% | -| improved-transparency-26.3 | DirectVulkan | +8.8% | **+13.1%** | +13.3% | -| minecraft-1.21.4-in-world | DirectGLES | +21.4% | **+18.2%** | +0.2% | -| minecraft-1.21.4-in-world | DirectVulkan | +11.8% | **+17.9%** | +19.7% | -| minecraft-1.21.4-fabric-sodium | DirectGLES | +10.9% | **+5.9%** | −0.1% | -| minecraft-1.21.4-fabric-sodium | DirectVulkan | +7.6% | **+8.3%** | +4.3% | - -splitctl−push 全部在 ±1.6% 内(增量来自 inproc 传输与 barrier,不是 APK / 构建差);p99 同向放大(sodium DirectVulkan 帧 p99 +107.7% 为离群尾帧)。inproc 臂 peak RSS 389 MiB – 2.5 GiB,全角色映射 560.5 MiB(256 MiB × 角色视图是虚拟映射容量,不是 RSS 增量)。 - -**fixture 受限的 8 组**:`iris-bsl-in-world` 只有 123 个 benchmark 帧,低于 200 帧尾规则,四臂 × 双后端验证全部失败(pull 同失败,是 fixture 上限不是回归);123 帧完整序列按同法折算成补充表 `ab-tables-bsl-123frame-supplement.md`:barrier tax CPU p50 DirectGLES **+7.1%**、DirectVulkan **+9.1%**,不混入上表。性能仍只记录、不作阻塞门。 - -## 8. P5c(`11ac3de6..b88e8487`,同日收官) - -出口门(E-P5c)实测: - -| 门 | 结果 | -|---|---| -| 守卫开启下 unit | **2187/2187**(strict `MOBILEGL_IPC_STRICT_ERRORS=1` 下同绿) | -| 守卫开启下 `integration-split` | **111/111**(107 + ct 的 4 条 CtWireScenario,全真跑) | -| 每层守卫 red-once | layer-1(MipmapStorage 守卫短路 → `TextureMapMipmapDataFromTheApplyThreadIsFatalByName` 红)与 layer-2(`RefusePipeInputsTouchWhileApplierOwnsIt` 短路 → `ClientPipeInputsFillWhileTheApplierOwnsItIsFatalByName` 红)各红一次并复原;tx 的采纳改回丢指针 → 恰 `StagedTextureProductionTest` 红;ev 的 writeback 改回裸指针 → `AtomicCounterScenario.SubDataAfterDispatchSurvivesAnImmediateReadback` 按名红;ct 的发射短路 → Ct 4/4 按名红;rv 的发射门短路 → 7 条按名红 | -| 纹理 `0xDD` audit | bsl in-world(100000 调用)与 iris-complementary in-world 全程零 Fatal | -| `SEG_EVENT` 往返 | 三种事件 + `kEventGlError` 各有单元往返;排空点 `eventDropped == 0` | -| `rsp` 分类 | 值类 = 0(rv 的 FieldOwnershipTest 钉住);按帧实测:bsl 948.5/帧(38.7/draw,123 帧)、complementary 1591.9/帧(151 帧)、iterationrp 286.2/帧(108 帧后止于既有具名 `texture-remint-pull`);残留全为对象类 15 行 | -| 普查(integration-gpu @ inproc,对同机 11ac3de6 基线逐名比对) | 基线 253 红 / 收官头 423 红;183 条 newly-failing 中 11 条为三个真回归(默认 FBO 格式时序、大 writeback 溢出、XFB scatter 读前端)——已修并回归绿;其余 172 条全部带 Fatal 名证据归类为设计红(传输下旧臂具名拒绝的对照车道 14 条 + Magma P7 未迁移面 156 条)与 2 条 DirectVulkan CtWire(改按名 skip:Magma 无 object_death 生产者,P7) | -| G1(pull 符号恒等,`11ac3de6` ↔ 收官头,CI 同款 sym 构建) | 0 增 / 0 删 / **3 认定 resize** / 0 重命名,`.text` −16 B:`SwapchainObject::Create`(ev 表面事件化)、`CopyTexSubImage2D`(hd 传输臂)、`ScopedRestartIndexSubstitution`(server-shadow 臂);pull 构建零 `MG_Remote` 符号 | -| G5(p3a / p4a 保护区) | 字节一致 | -| 生成器 / 卫生门 | 双生成器 --check/--self-test 绿;doc 引用、include 闭包、dirty-surface 绿 | -| Redmi 四臂复测 | **未做**(记录项,需设备窗口;barrier tax 重测随之) | -| 79 trace 普查 | **未重跑**(全集语料不在本机;P5b 的 72/6/1 仍以其头为准) | - -审计的 59 处直接访问的终态:纹理纹素 / 形状 / 脏区改读 server staged shadow(tx);反向通道四条事件(ev);句柄解析全部按记录(hd);`applier_reset` / `object_death` 上 wire(ct);值类残余读清零(rv);双层角色守卫 + `InBarrierWait`(gt)。**未到期的具名债**:`MGPipeReverseAnnouncementScope`(绑定记录 P4b 才发射的 ensure/通告族)与 `MGPipeFrontendKeyedRegistryScope`(G6 前端键 twin registry,P3b/P4b 重键)两个 scope 内的只读探测,以及对象类 15 行 BARRIER-PULLED——全部具名、可 grep、有退役阶段。 - -## 9. P5d(`cb06538c`、`56a77348`、`1f8de61b`;Redmi `2f7cbe2e`,FCL + Minecraft 26.3-rc-3 世界 "test",VD12,DirectGLES) - -协议:CPU 定频(大核 1958400 / 小核 1555200,`pin_device.sh`)、风扇二档、世界内 20 s 静置后取 30 s 窗口;fps = `MGPipe stats:` 行的帧数 / 时间(应用侧 swap 计数),逐线程 CPU = `/proc//task/*/stat` 的 utime+stime 差 / 帧数。两个已知的不受控变量:GPU 由厂商守护进程在每次启动时把 `min_pwrlevel` 打回 12(inproc 各臂 GPU 都在 342 MHz 空转、CPU 受限,不受影响;monolith 或受 GPU 或 120 Hz vsync 封顶);内核对 app 线程忽略 `sched_setaffinity`,client GL 线程常落在中核(cpu2/4/5)、apply 线程常驻大核 cpu7。因此配对比较以 **client 线程 CPU ms/帧** 为主指标。 - -| 构建 | 臂 | fps p50 | client ms/帧(核) | apply ms/帧(核) | 备注 | -|---|---|---|---|---|---| -| 二轮头 `56a77348` | inproc | 64.3 | 15.03 (cpu5) | 12.42 (cpu7) | 起点;`SPIN_US=2000` 68-73(三次),`=10000` 63,`SERVER_AFFINITY=off` 65.9 | -| 二轮头 `56a77348` | monolith | 116.5 | 5.85 (cpu7) | — | 75% 忙,vsync 120 Hz 封顶 | -| 三轮审查前构建 | inproc | 114.0 | 8.68 (cpu4) | 6.93 (cpu7) | `SPIN_US=2000` 112.3(不再有收益) | -| 三轮审查前构建 | monolith | 115.7 | 5.70 (cpu5) | — | 封顶 | -| 三轮(本提交) | inproc | 103-106 | 9.2 | 7.0-7.4 | 交错会话,见下 | -| 三轮(本提交) | monolith | 115(封顶)/ 206(一次未封顶) | 5.0-6.2 (cpu7) | — | 三次:115.3 / 115.1(120 Hz 封顶)与 205.8(未封顶) | - -交错会话(三轮头,post-review 构建,同一次定频、同一天): - -| 臂(顺序) | fps p50 (min / max) | client ms/帧(核) | apply ms/帧(核) | 备注 | -|---|---|---|---|---| -| mono1 | 205.8 (110 / 280) | 5.0 (cpu7) | — | 未封顶的一次;窗口内 110→280 摆动 | -| inproc1 | 106.1 (91 / 112) | 9.24 (cpu4) | 7.0 (cpu7) | | -| mono2 | 115.3 (108 / 117) | 6.2 (cpu7) | — | 120 Hz 封顶 | -| inproc2 | 103.3 (82 / 111) | 9.2 (cpu5) | 7.4 (cpu7) | | -| inproc3 | 101.9 (83 / 109) | 9.1 (cpu5) | 7.3 (cpu7) | | -| mono3 | 115.1 (109 / 117) | 6.1 (cpu7) | — | 封顶 | - -审查前构建(同一天早些、机身 50.8 °C 起)inproc 114.0 / client 8.68 ms(cpu4)/ apply 6.93;post-review 构建 pmap 线流量与之逐字节相同(364.7 KB/帧),差在运行间漂移(机身 53-54 °C 起、中核放置)之内。 - -`wait[]` 计数(三轮加入 stats 行):审查前构建 inproc 30 s 窗口累计 srv=7.56M / srvpark=76k、cli=6.70M / clipark=17k;`SPIN_US=2000` 下 park 各降两个量级、fps 不变——park 已不是主项。持久映射线流量 pmap ≈ 0.36 MB/帧(二轮 0.23,三轮多了首推与脏页排空)。 - -## 10. P5e(wave 1 `f6cfcbd3` → wave 2 `44f91c74` → fix1 `66621767` → ID-110 `3cc4e1ec`) - -P5e 尚未收官(`kMGPipeP5eRunAheadReady` 仍为 false),本节记的是**设备验证一轮**,问题是"八包合并后两条传输臂还能不能正常跑游戏",不是配对性能。 - -### 主机门(WSL `~/w7/p5e-int`,split flavour,头 `3cc4e1ec`) - -| 门 | `44f91c74` | `3cc4e1ec` | -|---|---|---| -| unit | 2250/2250 | **2250/2250** | -| `integration-split` | 113/113 | **116/116** | -| `integration-gpu`(两条运行时臂,ID-109 起为常设步) | **1157/1294**(137 SEGFAULT / 38 scenario) | **1294/1294** | -| `integration-split-strict` | 7/116 | 7/116(预期红) | - -`integration-gpu` 那一列是本阶段最重要的一个数:`44f91c74` 的 137 个 SEGFAULT 在当时**没有任何门禁步骤会看到**——`integration-split` 的每一条都导出 `MOBILEGL_TRANSPORT=inproc`,而 push 构建有两条服务端臂。详见 `CURRENT_STAGE_PROGRESS.md` §2.5。 - -### 设备(Redmi `2f7cbe2e`,FCL fordebug + Minecraft 26.3-rc-3 世界 "test",VD12,DirectGLES;进世界后 60 s 采样) - -| 臂 | 进世界 | fps | draws/帧 | 进程 | Fatal / crash buffer | -|---|---|---|---|---|---| -| monolith(`44f91c74`,修复前) | — | — | — | **65 s 后进程消失** | SIGSEGV,栈见 §2.5 | -| monolith(`3cc4e1ec`) | 31 s | 263.5 | 845 | 存活 | 无 | -| inproc(`3cc4e1ec`) | 31 s | 136.1 | 853 | 存活 | 无 | -| inproc 复跑(`3cc4e1ec`) | 32 s | 140.3 | 852 | 存活 | 无 | - -monolith 这一次未被 120 Hz vsync 封顶(与 §9 的 205.8 那次同类),两臂 fps 不可直接相比。inproc 的 136-140 与修复前记录的 140.0 / 144.9 同档:fix1 改的那行两臂都读,split 臂没有被拖慢。 - -### 测量纪律(本轮与上一轮踩到的坑,全部是污染而非代码问题) - -- `adb install` 的收尾会在一两分钟后杀掉正在跑的同包进程(`am_kill … due to installPackageLI`):装包与测量之间必须等到 `dex2oat` 退出。 -- 派给外部 CLI 的任务,其进程会活过 harness 的完成通知;**派出后不得自己再跑同一件事**,否则两套实验同时对一台手机 force-stop / 清 logcat,得出假失败。 -- 不得在脚本正被 bash 执行时修改它(边读边执行);要改先冻结副本。 -- 游戏内 F3 的 `GIT@` 戳记在增量构建下是旧的,**判断库版本要看 APK 里 `.so` 的符号 / 字符串**,不看戳记。 - -## 11. P5e wave 3 — run-ahead 武装后的设备矩阵(头 `25fba0d5`) - -Redmi `2f7cbe2e`,FCL fordebug + Minecraft 26.3-rc-3 世界 `test`,VD12,DirectGLES,~850 draws/帧。 -**CPU 定频**(见下),GPU 钉 pwrlevel 0 且每臂重新断言,风扇开,进世界后 20 s 稳定再取 30 s 窗口,臂交错。 - -### 定频,以及为什么这一节必须先讲它 - -第一轮矩阵没有定频,于是 **monolith 自己那一臂**的逐帧 CPU 在两次运行之间从 4.43 ms 跳到 7.07 ms, -而慢的那次**温度更低**(53.5 °C vs 58.9 °C)——不是热降频,是 walt governor 把 policy0 从 2745 MHz -拉到 748 MHz。未定频的逐线程 CPU ms/帧**不能跨臂比较**,那一轮矩阵作废。 - -`tools/device_bench/pin_device.sh` 不认识本机 serial,并**明确拒绝猜**("pin path 因 SoC 而异,猜错会静默失败")—— -这是对的,也正是它没有静默半应用的原因。按本机实测节点另写 `pin_redmi.sh`:写序 min→硬件底、max→目标、 -min→目标(顺序要紧,否则按 stock 相对目标的位置会半应用),并**回读 `scaling_cur_freq` 确认**,跑完再 check 一次。 - -### 结果(每臂两次,离散度 < 2%) - -定频 little 1555200 / big 1958400: - -| 臂 | fps p50 | fps max | client ms/帧 | apply ms/帧 | -|---|---|---|---|---| -| monolith | 115.1 | 117.0 | 6.19 | — | -| inproc run-ahead | 117.6 / 117.9 | 118.3 / 118.6 | 6.71 / 6.63 | 4.30 / 4.32 | -| inproc lockstep (`MOBILEGL_IPC_RUN_AHEAD=0`) | 111.1 / 110.7 | 114.9 / 115.1 | 8.27 / 8.09 | 6.44 / 6.41 | - -定频 little 1996800 / big 1958400(更高会被厂商限幅器夹住): - -| 臂 | fps p50 | fps max | client ms/帧 | apply ms/帧 | -|---|---|---|---|---| -| monolith | 115.6 / 115.4 | 117.2 / 117.5 | 6.45 / 6.25 | — | -| inproc run-ahead | 117.6 / 118.0 | 118.5 / 118.6 | 6.92 / 6.87 | 4.43 / 4.35 | -| inproc lockstep | 111.6 | 113.7 | 8.16 | 6.53 | - -### 读法 - -- **配对 A/B**:`MOBILEGL_IPC_RUN_AHEAD=0` 与默认是同一构建、同一份记录、同一定频,唯一差别是客户端等不等 - (ID-114:**不得**用 `VERB_BARRIER=0`,它是 `RunAheadArmed()` 的第一个合取项,会顺手关掉 run-ahead, - 而且实测几秒内即 `Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@ClientWaitSync"}`)。 - 差值:client **−18%**、apply **−33%**、fps p50 **+6%**。 -- **两个定频档下 monolith 与 run-ahead 都撞到 120 Hz 面板上限**(max 117-118.6),**只有 lockstep 撞不到** - (max 113.7-115.1)。所以"inproc 是否追平 monolith"在本机真实负载上的答案是**追平了**;阶段开始时是 - **0.62-0.74 倍**(见 §10)。 -- 逐帧 client CPU:run-ahead 6.9 / monolith 6.3,多约 **8%**,两者都在上限之下有余量。 -- **未回答**:面板上限之上谁更快。本机定不住更高频率;一次未定频的高频窗口给出 monolith p50 201 / - run-ahead p50 181(约 1.10 倍),但那次不可配对,只作方向性提示。需要关 vsync 或换上限更高的设备。 - -### 侧写(`25fba0d5` 之前,`7c6f6886`,说明 run-ahead 为什么值得做) - -symbolized `simpleperf cpu-cycles`,客户端 GL 线程:`SessionProducer::WaitForAppliedOrEventBacklog` -**31.58% 自身时间**,其后第二名只有 3.72%。设备计数器同期显示客户端每帧进入 doorbell 等待约 **916 次** -(对 ~849 次 draw),其中 99.8% 靠自旋解决——`MOBILEGL_IPC_SPIN_US=0` 让每次等待都 park,帧率塌到 20.9。 -所以代价是**会合本身**,不是自旋参数,这也是为什么修法是"不再每次 draw 会合"而不是"把每次会合做便宜"。 - -### 11.1 渲染距离 32 的对照(同头 `25fba0d5`,~3550 draws/帧) - -§11 的矩阵在 VD12 下跑,而两条臂都撞到了 120 Hz 面板上限,所以那一节回答的是"能不能撞到上限", -不是"谁更快"。把渲染距离拉到 **32**(`options.txt` 的 `renderDistance`,备份为 `.p5e-backup`,测完已还原) -负载涨到 **~3550 draws/帧**(VD12 的 4.2 倍),**两条臂都远离任何上限**(游戏自身 `enableVsync:false`、 -`maxFps:260`,实测 max 只有 37-71),这才是可比的一轮。 - -条件:CPU 定频 little 1555200 / big 1958400(跑前跑后各 check 一次,均 PINNED),GPU 钉 pwrlevel 0, -风扇开,进世界后**静置 75-150 s**再取 40 s 窗口——VD32 的区块流式加载远比 VD12 长,沿用 20 s 静置会把 -加载期算进稳态。臂交错。 - -| 臂 | n | fps p50 中位数 | p50 范围 | client ms/帧 中位数 | apply ms/帧 | -|---|---|---|---|---|---| -| monolith | 5 | **58.5** | 56.3 – 67.9 | 16.19 | — | -| inproc **run-ahead** | 5 | **59.9** | 56.8 – 61.1 | **15.72** | 11.1 – 12.1 | -| inproc lockstep(`RUN_AHEAD=0`) | 2 | 35.5 | 34.5 – 36.5 | 26.17 | 19.7 – 20.5 | - -**结论:在 VD32 下 inproc 与 monolith 齐平**,中位数上 run-ahead 甚至略微领先(59.9 vs 58.5, -client CPU 15.72 vs 16.19),差值远在 monolith 自身的离散度之内——**monolith 五次的范围是 56.3-67.9 -(±10%),run-ahead 是 56.8-61.1(±4%)**,也就是说 split 臂不但追平,还更稳。`mono1` 的 67.9 是全场唯一 -的离群值(装包后第一次运行)。 - -**run-ahead 相对它取代的 lockstep:p50 +69%、client CPU −40%、apply CPU −43%。** -负载越重它越值钱,这是预期内的——会合次数随 draw 数走,VD12 是每帧约 900 次,VD32 约 3600 次。 - -两条臂**都是 client 线程打满**(run-ahead 15.72 ms CPU / 16.7 ms 帧 ≈ 94%;monolith 同理),所以这一列 -是各自真正的瓶颈,可以直接比。 - -**下一个机会,不是缺陷**:apply 线程只有 11.1-12.1 ms,client 有 15.7 ms——**两侧不平衡**。把工作从 client -挪到 apply 会直接降低瓶颈。这在 lockstep 下毫无意义(两侧串行相加),run-ahead 之后才成立,是本阶段 -解锁出来的新优化方向,留给后续阶段。 - -## 12. P6 出口测量(2026-09-22,Redmi `2f7cbe2e` + WSL 桌面) - -P6(spawn transport)的实现包(a6 / c6 / lk / so / sm / cp / hs / dl / st / t6)已全部落地,出口门 1–6 的逐项证据见 `CURRENT_STAGE_PROGRESS.md`;本节记录门 7(真机配对 A/B 热窗口)与门 8(三个强制性能数)在 2026-09-22 采齐的实测,以及支撑它们的收尾代码与工具。按本文件既有口径,下面所有性能数**只记录、不设门**:门 7 的判读是 tie,门 8 的三个数不带阈值。 - -### 12.1 协议(本节两个设备会话共同遵守) - -| 项 | 值 | -|---|---| -| 设备 | Redmi `M332BF`,serial `2f7cbe2e`,SM8750 / Adreno 830v2 | -| reboot-clean | `adb reboot` + `sys.boot_completed=1`,**boot id 前后比对**(id 变了才算数,见 §12.2) | -| CPU 定频 | little `policy0` → 1555200,big `policy6` → 1958400(`pin_device.sh 2f7cbe2e pin`;每臂前 `pin`、后 `check`,非 `PINNED` 即作废该臂,不是降级) | -| GPU 钉频 | kgsl `min/max_pwrlevel = 0` → **1050000000 Hz**(可比性警告见 §12.7) | -| 风扇 | `/sys/class/xm_power/hw_monitor/pwm_fan` `target_level=2` | -| 同热窗口臂交错 | 每个会话都在**一次 boot 内的唯一热窗口**里按给定顺序交错跑完,臂间有冷却 | -| 负载 | `minecraft-1.21.4-rd12-odinlite-in-world`,DirectGLES,251 帧,**1471.36 draws/帧** | -| 跑法 | `--benchmark-repeats 3`(门 7)/ `2`(门 8),`--benchmark-tail-frames 200`,`--benchmark-no-finish`,best-of-3 / best-of-2 | -| split 开关 | `MOBILEGL_IPC_ROLE_SPLIT_STATE=1`、`MOBILEGL_IPC_STRICT_ERRORS=1`、`MOBILEGL_IPC_RUN_AHEAD=1` | -| stats | `MOBILEGL_PIPE_STATS=1`、`MOBILEGL_PIPE_STATS_PERIOD=120` | -| 源码头 | 门 7 会话 `75dd0cb1`;门 8 两会话 `94c130d6` + §12.6 的收尾改动(工作树) | -| 臂的证明 | 每个 spawn 臂必须在**自己的**私有日志里出现 `Config: MOBILEGL_TRANSPORT=spawn` 与 `spawn ARMED - the server role runs in pid N`;`--transport` 在 `--benchmark` 模式下被 runner 忽略(会静默跑成 monolith),所以传输一律用显式 `--env MOBILEGL_TRANSPORT=…`,跑完按库自己的日志核对 | - -**主指标 = client GL 线程自身的逐线程 CPU ms/帧**(§9 的规则:内核对本 app 的 `sched_setaffinity` 请求不生效,进程总量会把两个时钟域混在一起),由设备在**尾 200 帧**上汇总;`fps` 与 wall p50/p95 并列记录,不作主指标。 - -**帧分母取自哪条日志。** `frames=` 由 `PipeStats::OnPresent` 递增,而它是 backend 的帧边界:`spawn` 下 present 在 **server 进程**被应用,所以 spawn **client 的 `frames=0 window=0`**——它一个窗口都不推进。因此费率一律除以**真正数到帧的那条日志**的 `frames=`(inproc 取 client log,spawn 取 server log,两者都是 251),**不得**除以同一行的 `window=`,那会把费率抬高 run/window(本负载约 21×),得到一个看似合理而错的数。同理: - -- spawn client 行里的 `srv=0 srvpark=0` 与 spawn server 行里的 `cli=0 clipark=0` 都是**「另一个进程」的零,不是「从未等待」**;`srv`/`srvpark` 由 apply 线程发布进**它自己进程**的 PipeStats,`cli`/`clipark` 由 client 的发射器发布进 client 进程。 -- spawn client 的 `cli`/`clipark` 仍然可信,因为 `EmitPresent` 走 `PublishGauge`(**store 不是 add**),run total 与窗口无关;而它的窗口化字段(`draws=`、`wrec=`、`bytes[...]`、`gates[...]` 及所有 `*/f`)在同一次运行里**无效**。 -- spawn client 整轮只有 `Shutdown` 的**一条终行**(benchmark 模式)或**一条都没有**(snapshot 模式:该模式在快照完成后抛出退出,跳过 `retrace::cleanUp()` → `MobileGL::Destroy()` → `PipeStats::Shutdown()`)。 -- `* total ms` 那类**每角色运行总量**包含第 1 帧的 shader 编译(本 fixture 的帧 1 花 **7332 ms** client CPU,其余 250 帧各 7.7–8.5 ms),**不是**每帧率,不得除以 251;它跨臂差 4.5–6.7% 与传输无关,这正是主指标取「尾 200 帧的逐帧值」的原因。 - -### 12.2 门 7:配对 A/B(两会话) - -**会话 1**(boot id `e69d0329-2264-4a2d-9b00-e1a4ffb6154d` → `107f24d3-b1f1-4a97-ab8c-9706d1901f32`):六臂交错 `monolith, inproc, spawn, inproc, monolith, spawn` × best-of-3,**18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal{`**,12 次 pin check 的 `cpuss-0-0` 温度 37.6–46.5 °C。 - -| 臂 | 顺序 | wall p50 ms | **client CPU p50 ms/帧** | fps | 臂内散布 | -|---|---|---:|---:|---:|---:| -| monolith (a) | 1 | 11.320 | **11.231** | 86.32 | 1.0% | -| inproc (a) | 2 | 7.971 | **7.880** | 106.04 | 0.3% | -| spawn (a) | 3 | 7.989 | **7.876** | 106.12 | 2.2% | -| inproc (b) | 4 | 8.001 | **7.889** | 105.78 | 0.2% | -| monolith (b) | 5 | 11.287 | **11.240** | 86.26 | 1.3% | -| spawn (b) | 6 | 7.979 | **7.869** | 105.95 | 4.2% | - -**会话 2**(boot id `107f24d3-b1f1-4a97-ab8c-9706d1901f32` → `7e6c6e9f-1894-4916-bf39-cce48c9abd37`):只跑 inproc / spawn 交错 `inproc, spawn, spawn, inproc` × best-of-2(聚焦门 8 ①,见 §12.3),**4/4 runner 退出 0、8/8 pin check `PINNED`、0 `Fatal{`**,温度 37.2–44.9 °C。 - -| 臂 | 顺序 | wall p50 ms | **client CPU p50 ms/帧** | fps | 臂内散布 | -|---|---|---:|---:|---:|---:| -| inproc | 1 | 8.105 | **7.987** | 105.72 | 0.14% | -| spawn | 2 | 8.116 | **7.992** | 104.43 | 0.34% | -| spawn | 3 | 8.113 | **8.009** | 104.19 | 0.26% | -| inproc | 4 | 8.089 | **7.989** | 103.72 | 0.69% | - -**配对差(spawn − inproc)**,两个顺序都列,不挑好看的那个: - -| 会话 | 指标 | inproc (a / b) | spawn (a / b) | Δ(均值) | Δ% | -|---|---|:---|:---|---:|---:| -| 1(best-of-3) | **client CPU p50 ms/帧** | 7.880 / 7.889 | 7.876 / 7.869 | **−0.012** | **−0.15%** | -| 1 | wall p50 ms | 7.971 / 8.001 | 7.989 / 7.979 | −0.002 | −0.03% | -| 1 | fps | 106.04 / 105.78 | 106.12 / 105.95 | +0.125 | +0.12% | -| 2(best-of-2) | **client CPU p50 ms/帧** | 7.987 / 7.989 | 7.992 / 8.009 | **+0.0125** | **+0.16%** | - -**monolith 基线行**(不属配对,按协议记录): - -| 比较 | client CPU p50 ms/帧 | Δ | fps | Δ | -|---|---|---:|---|---:| -| monolith → inproc | 11.236 → 7.885 | **−29.8%** | 86.29 → 105.91 | +22.7% | -| monolith → spawn | 11.236 → 7.873 | **−29.9%** | 86.29 → 106.04 | +22.9% | - -**判读:tie。** 会话 1 的差值(−0.15%)落在臂内散布 0.2–4.2% 之内,且 wall 列的**符号随顺序翻转**(顺序 (a) inproc 领先 0.03%、顺序 (b) spawn 领先 0.03%),这是噪声的特征而不是效应的特征;四个 inproc / spawn 臂的 client CPU p50 都落在 7.87–7.95。会话 2 在同一负载、不同构建与不同 boot 上复现了这个 tie(+0.16%,臂内散布 0.14–0.69%,差值比它取自的臂内散布低一个数量级)。两会话之间移动的是绝对水平(约 0.1 ms),那是运行间漂移,不是传输效应。**两条 transport 在 client 每帧 CPU 上不可区分,两者都比 monolith 约便宜 30%**——spawn 是通过 socket 拿到这个数的,**这条负载的分辨率看不见 socket 门铃的代价**。性能只记录,**不设门**。 - -### 12.3 门 8① socket 门铃 vs inproc condvar - -四个计数器:`srv` / `srvpark` = apply 线程进入 `Doorbell::Wait` 的次数 / 其中停止自旋转为阻塞的次数;`cli` / `clipark` = client 生产者的同一对。**两对都是运行总量**,所以费率除以该轮运行的 `frames=251`,**不是**同一行的 `window=`(见 §12.1)。 - -**会话 2 的完整账**(这次 spawn 的 server 半边也在——见 §12.6 的修复): - -| 臂 | 传输 | counters 取自 | frames 取自 | `cli frames=` | `srv frames=` | srv | srvpark | cli | clipark | **srv/f** | **srvpark/f** | **cli/f** | **clipark/f** | -|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| -| 01 | inproc | client log(同进程两角色一行) | client log | 251 | 240 | 2 869 874 | 1 341 | 30 407 | 1 444 | **11433.8** | **5.343** | **121.1** | **5.753** | -| 02 | spawn | client(cli) + server(srv) | server log | 0 | 251 | 2 614 177 | 1 229 | 30 401 | 1 524 | **10415.0** | **4.896** | **121.1** | **6.072** | -| 03 | spawn | client(cli) + server(srv) | server log | 0 | 251 | 2 614 456 | 1 170 | 30 401 | 1 525 | **10416.2** | **4.661** | **121.1** | **6.076** | -| 04 | inproc | client log | client log | 251 | 240 | 3 002 771 | 1 265 | 30 407 | 1 406 | **11963.2** | **5.040** | **121.1** | **5.602** | - -**会话 1 的账**(同一个窗口里的另一轮,spawn 的 server 半边当时**采不到**): - -| 臂 | 传输 | srv | srvpark | cli | clipark | **srv/f** | **srvpark/f** | **cli/f** | **clipark/f** | -|---|---|---:|---:|---:|---:|---:|---:|---:|---:| -| inproc (a) | inproc | 2 738 247 | 1 543 | 30 407 | 1 548 | **10909.4** | **6.148** | **121.1** | **6.169** | -| spawn (a) | spawn | **不可得** | **不可得** | 30 401 | 1 538 | — | — | **121.1** | **6.127** | -| spawn (b) | spawn | **不可得** | **不可得** | 30 401 | 1 530 | — | — | **121.1** | **6.096** | -| inproc (b) | inproc | 2 727 377 | 1 281 | 30 407 | 1 399 | **10866.0** | **5.103** | **121.1** | **5.574** | - -会话 1 的 spawn 半边不可得是**结构性**的、不是漏采:spawn server 进程当时从不跑 `PipeStats::Init()`(它只从 `MobileGL::Initialize()` 调用,而 spawn server 走的是 `MG_ConfigLoader::Init()` + `MG_Backend::InitServerRoleForSpawn()`),于是该进程里所有 `PipeStats::Enabled()` 门恒为 false,**一条汇总行都没有**——不是携带 0,是整行不存在。client 日志里的 `srv=0 srvpark=0` 因此是「另一个进程」的零。**该缺陷已在工作树修好**(§12.6),会话 2 就是修后的复采。 - -**逐列读法:** - -- **`srv/f`(server 会合率)**:inproc `10 866–11 963`,spawn `10 415–10 416`;折算成每 draw,spawn 两臂各 **7.1**,inproc 两臂 **7.8 / 8.1**(1471.36 draws/帧;会话 1 的 inproc 两臂相应是 **7.4**)。spawn 比 inproc **低 8.9–12.9%**,但这不是传输的胜利:两个计数器数的**不是同一个事件**——inproc 下 apply 线程的 `Doorbell::Wait` 同时是同地址空间内发布 client 队列排空的机制、client 在 verb barrier 后面大段同步,spawn 下同一个循环是被描述符就绪唤醒;每帧两个角色必须达成一致的次数是**handoff 纪律**的性质,不是门铃的代价。可比的是 park 比例,而它两边几乎相同。 -- **`srvpark/f`(会合真的阻塞了多少)**:会话 2 inproc 5.343 / 5.040、spawn 4.896 / 4.661;会话 1 inproc 6.148 / 5.103(spawn 不可得)。两个会话合起来即**服务端 5.0–6.2 parks/帧、其中只有 0.042–0.06% 的等待进入阻塞**(会话 2 四臂逐臂 1 341/2 869 874 = 0.047%、1 229/2 614 177 = 0.047%、1 170/2 614 456 = 0.045%、1 265/3 002 771 = 0.042%;并入会话 1 的 inproc 两臂后上界到 0.056%)。自旋预算在两条传输上几乎覆盖全部会合——P5d/P5e 的结论在这里再次成立:代价是**会合本身**,不是唤醒。 -- **`cli/f`(client 自己的等待)**:**四个臂上都是 121.1**(会话 1 的四个臂也一样,运行总量 30 401–30 407 次),逐字到十分位。client 两种传输做同样的工作,传输不改变它**多久**等一次。 -- **`clipark/f`(client 的 park)**:**唯一 socket 略差的列**——会话 2 spawn 6.07(6.072 / 6.076)对 inproc 5.75 / 5.60,每帧多约 **0.4 次 park**(~121 次等待里的 0.4 次)。client 等待的 **4.6–5.1%** 会 park,比服务端高两个数量级,因为 client 的自旋预算花在一个经常还没到的生产者上。 -- **tie 判读**:client CPU 差 **+0.16%**(会话 2)落在 0.14–0.69% 的臂内散布内,会话 1 是 −0.15%;契约 §9 那条注记(「把 ~1600 spins/帧 转成 ~4 次阻塞读,**可能更便宜**」)的观测对象正是 client CPU ms/帧,而它**两个方向都没动**。**结论是诚实的:socket 阻塞读既不更便宜、也不更贵**——该注记的「更便宜」未兑现。注记里的 1600 spins/帧 是更重场景的数,本机 rd12 的参照是服务端 ~10 400–12 000 次会合/帧、client 121 次/帧;park 比例 0.042–0.06%(服务端)与 4.6–5.1%(client)也说明本场景的自旋几乎全都奏效。 -- 两条传输**都比 monolith 约便宜 30%**(§12.2),spawn 走的是一条 socket 而不是共享地址空间。 - -### 12.4 门 8②③:`SEG_STAGE` 字节/帧、分块后记录/帧、`maxrec` 与逐 blob 分布 - -口径:`MOBILEGL_PIPE_STATS=1` + `MOBILEGL_PIPE_STATS_PERIOD=1`(每个 `eglSwapBuffers` 一行,`window=1` 恰一帧),车道 = inproc + `MOBILEGL_IPC_ROLE_SPLIT_STATE=1` + `MOBILEGL_IPC_STRICT_ERRORS=1` + `MOBILEGL_IPC_RUN_AHEAD=1`,后端 DirectGLES,真实 trace retrace。两个负载:**OpenRA**(`MobileGLTraceReplay.OpenRA.DirectGLES.SPLIT`,SSIM **1.000000**)与 **rd12-odinlite-in-world**(无 SPLIT ctest 条目,按同一命令行手工驱动,SSIM **0.999998**)。新增的两个计数器都落在**唯一的收口点**上:`SEG_STAGE` 字节记在 stage 分配器(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:878`,`StageAllocate`),记录数记在记录提交路径(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1188`,`EncodeRecord` 成功路径)——所以覆盖是构造性的,不是一张「今天有哪些 producer」的名单。(两处行号取 §12.6 收尾改动后的工作树内容。)这两个数在 **inproc 车道**采集;spawn 下 client 的字节 / 记录类同样在采(它们由 client 的发射器 Add,run total 可信),但 spawn 的**服务端**字节 / 记录类要等 §12.6 的 `ServerMain.cpp` 修复之后才存在。 - -**framing 警告(两个读数都报)**:`PERIOD=1` 下**第一行覆盖整个启动 / 加载窗**——rd12 的帧 1 有 **981 654 draws**、5 053 895 条记录、`seg=2 317 939 359`,而其余每帧只有约 1 471 draws。所以「窗口求和 / 帧数」是**含加载的均值**;报这个数时必须说清是哪个口径。rd12 的加载窗把 run-wide 均值抬到稳态均值的**约 7 倍**(10.79 vs 1.53 MB/帧);OpenRA 两个值接近(8.07 vs 8.01 MB/帧)是因为它的加载窗与稳态窗同量级。 - -| 口径 | OpenRA | rd12 in-world | -|---|---:|---:| -| **`SEG_STAGE` 字节/帧——稳态中位数** | **130.8 KB/帧**(130 848 B) | **816.2 KB/帧**(816 248 B) | -| **`SEG_STAGE` 字节/帧——稳态均值**(丢弃前 10% 窗) | **8.01 MB/帧**(8 012 890 B) | **1.53 MB/帧**(1 534 555 B) | -| `SEG_STAGE` 字节/帧——含启动负载的均值 | 8.07 MB/帧(8 068 255 B) | 10.79 MB/帧(10 792 107 B) | -| **记录/帧(分块后)——稳态中位数** | **211** | **7 497** | -| **记录/帧(分块后)——稳态均值** | **206** | **7 504** | -| 记录/帧——含启动负载的均值 | 196.8 | 27 689.5 | -| 参考:`draws` 稳态中位数 | 30 | 1 471 | -| `maxrec` / `maxcap`(运行总量) | 1808 B / 4 194 304 B(0.043%) | 1808 B / 4 194 304 B(0.043%) | -| 运行总量 `seg` / `wrec`(帧数) | 233 979 394 / 5 706(29 帧) | 2 698 026 763 / 6 922 378(250 帧) | -| `ringwraps` / `ringpads` / `ringwaits` | 0 / 0 / 0 | 61 / 53 / 0 | - -读法: - -- **两个负载的稳态形状完全不同,这正是必须分开报的理由**:OpenRA 稳态的 `tex` 中位数是 32 768 B 而均值 5.6 MB(少数窗有巨大的纹理上传尖峰),`seg` 的中位数 / 均值因此差 **61×**;rd12 稳态的 `tex` 中位数为 0(多数帧不上传纹理),差 **1.9×**。只报一个数就会掩盖另一个负载的形状。按本文件同类数字的惯例,稳态中位数与稳态均值这对都列出来。 -- **`maxrec=1808` 与分块前完全相同,原因不是「分块没生效」而是「分块没有机会生效」**:默认 8 MiB chunk 预算(`MGPipeStageChunkBytes()` = `clamp(segment/4, 4096, segment)`,默认 32 MiB → 8 MiB)在这两条负载上从未被打到,所有 blob 都是原始尺寸。稳态 `seg/wrec ≈ 816 248/7 497 ≈ 109 B/条`,与全局的 390 B/条都在字节级别。§6.3 记的分块前 `maxrec=1808` / `SetVertexAttribDefaults` 因此原样成立;`ringwaits=0`(250 帧一次都没等过 retiredSeq)与 rd12 稳态 `seg` 中位数对 32 MiB 的 `SEG_STAGE` 约 40 帧余量一致。 -- **`wrec` 是窗口计数**(`PERIOD=1` 下每窗恰一帧,所以 `wrec` 与 `wrec/f` 同值),且 `PERIOD=1` 每帧印一行 INFO 本身有可观开销——**这些帧数不能当性能数**,只用于按帧摊平字节与记录。 -- **交叉核对(能排除什么、不能排除什么)**:`seg` 记 wire 写进 `SEG_STAGE` 的字节,后端的 `buf`/`tex`/`csob-blob` 记往驱动搬的字节,两个总体不同、不要求相等,但应在同一量级且同步起伏。逐窗(250 窗)比对:`seg` 中位数 818 030 / 均值 10 792 107,`parts`(= buf+tex+csob-blob)中位数 291 840 / 均值 3 978 204,均值比值 2.713,每条记录 389.8 B;两者在同一些窗一起抬升、同一些窗一起落底,没有出现「一个动另一个不动」的窗。比值 > 1 有解释(`seg` 稳态恒有 `csob-blob ≈ 281 KB/帧` 的 program archive 只走 wire 不经后端字节类,加上 persistent-map 推送与 tail 字节)。这**不能证明覆盖完整**(没有第三方真值),能排除的是「`seg` 明显偏离同一批字节」这一整类错误。 -- **逐 blob 字节分布的首次测量**:答案是「默认配置下该分布就是未分块的原始分布」。取数车道是 **integration**(`ctest -R "^DirectGLES\.(Arena|Ssbo|Buffer|Atomic|Readback)"`,19/19 通过),经 `MOBILEGL_PIPE_STATS_FILE` 的 JSON dump——**retrace 车道拿不到 dump**,因为 snapshot 模式在快照完成后抛出退出、跳过了 `retrace::cleanUp()` → `MobileGL::Destroy()` → `PipeStats::Shutdown()`(§12.1),所以 dump 只能在 benchmark 模式或 integration 车道取。该 dump:**70 records**、`seg=5768 B`,直方图复用了 draw payload 的桶边界(bucket 0 = 0 字节,bucket n>0 = [2^(n-1), 2^n))。**这里有两个不同的计数,必须写清,否则会被读成同一个数**:**非空 bucket 数 = 8**(落在 bucket 3–12),**blob 条数 = 12**;也就是说「**8 blobs / bucket 3–12**」这句简写里的 8 指的是**有内容的桶数**,而逐桶计数合计是 12 条 blob。逐桶:bucket 3 [4,8) 4 条、4 [8,16) 1 条、5 [16,32) 2 条、7 [64,128) 1 条、9 [256,512) 1 条、10 [512,1024) 1 条、11 [1024,2048) 1 条、12 [2048,4096) 1 条(4+1+2+1+1+1+1+1 = 12)。这批 integration 用例的 `frames=0`(跑 swap 但不走 Present 计数),所以窗口字段无意义,分布本身是**运行总量**(分布没有「窗口」可言)。 - -### 12.5 负控:`MOBILEGL_IPC_STAGE_MB=1`(256 KiB chunk 预算) - -要让分块真正发生,唯一的办法是把 stage 预算人为压小;同一条 rd12 trace、同一个库、只改这一个环境变量的 A/B: - -| rd12 in-world,inproc split | `STAGE_MB=32`(chunk 8 MiB) | `STAGE_MB=1`(chunk 256 KiB) | 变化 | -|---|---:|---:|---| -| SSIM | 0.999998213 | 0.999998213 | 不变 | -| **`seg` 运行总量** | **2 698 026 763** | **2 698 026 763** | **逐字节完全一致** | -| `seg` 稳态中位数 | 816 248 | 554 104 | −32% | -| **`wrec` 运行总量** | 6 922 378 | 6 929 736 | +7 358(**+0.11%**) | -| `wrec` 稳态中位数 / 均值 | 7 497 / 7 504 | 7 497 / 7 508 | +0.05% | -| `tex` 运行总量 | 641 567 296 | 1 886 392 128 | **+194%** | -| `buf` / `csob-blob` / `draws` | 见 §12.4 | 与 `STAGE_MB=32` 逐项相同 | 不变 | -| `maxrec` / `maxcap` / `ringwraps` / `ringpads` | 1808 / 4 194 304 / 61 / 53 | 同左 | 不变 | - -这是**它自己那个问题的负结果,如实记录**: - -- **`seg` 运行总量两侧逐字节相同**说明该计数器测的是**生产者交出来的内容**,与发射侧怎么切、arena 开多大**完全无关**——这正是把落点放在唯一收口点的直接证据:若记在 emitter 侧或记成 arena 占用,这一行就会跟着预算动。 -- **稳态中位数 −32% 与运行总量不变同时成立**:同样的总字节被**重新分到不同的窗口**(预算变小后,原本一次完成的上传被切成多次、跨帧重传),所以**逐窗归属会动、总量不会**。读数应以运行总量或明确声明的窗口口径比较,**不要**拿两套配置的单窗中位数直接对比。 -- **`wrec` 只涨 0.11%**(+7 358 条 / 250 帧 ≈ +29 条/帧,稳态中位数两侧相同)说明这条 trace 的内容本来就没有超过 256 KiB 的 blob,即使预算压到 256 KiB 也几乎无可切;`maxrec` 仍是 1808,第三次确认(`maxrec` 是单条**记录**的字节上界,blob 被切不改变它)。 -- **`tex` +194% 是这次 A/B 真正的发现,但不作结论**:`MOBILEGL_IPC_STAGE_MB` 同时是 **arena 大小**与 chunk 预算的来源,压小它改变了纹理 slab 的切分与重传行为(整宽 slab 被预算切碎后每片各自重传)。这一项**不在门 8 的定义里**,且**没有隔离到单变量**(arena 大小与 chunk 预算被同一个环境变量绑在一起),因此只作为 A/B 的可解释性证据记录,**不作结论**;要分开这两个效应需要把二者拆成可独立配置的量,那是另一个包的事。 -- 因此门 8 的「chunking 后记录/帧」取**默认配置**的 §12.4 值。 - -### 12.6 支撑本节的收尾代码与工具(工作树,未提交) - -| 改动 | 位置 | 内容与证据 | -|---|---|---| -| 新计数器 `ByteClass::StageSegmentBytes` | `MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:878`(`StageAllocate`) | stage 分配器的**唯一收口**;摘要行 `bytes/f[...]` 里印 `seg=` | -| 新计数器 `CallClass::WireRecords` | `MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1188`(`EncodeRecord` 提交路径) | 记录提交路径的**唯一收口**(`Reserve` 成功、正文写完、blob 槽诚实性检查之后、`++m_emitSeq` 之前);摘要行印 `wrec=` 与 `wrec/f=`,与 `draws/f=` 同形、按 `perFrame` 规则联动 | -| staged-blob 直方图 | `MobileGL/MG_Util/Metrics/PipeStats.{h,cpp}` | 复用 `PayloadBucketOf` 的桶边界,**运行总量**,经 JSON dump 出口(Tracy 画不了分布,与既有 `cmd-bytes-per-draw-histogram` 的选择一致) | -| spawn server 进程从未武装计数器 | `MobileGL/MG_Remote/Server/ServerMain.cpp`,`PipeStats::Init()` 加在 `MG_ConfigLoader::Init()` 与 `MGPipeSetServerProcessRole(true)` **之后**,配一行 `Shutdown()` 在 `loop.Stop()` 之后、`_exit(0)` 之前 | **红一次**:修复前 spawn server 在 `mobilegl.server.log` 里 **0 条**汇总行,修复后 **29 条**;SSIM 1.0 不变。三处顺序都是承重的:`Init()` 从 config latch 该开关(必须晚于配置解析)、晚于定角色(让汇总行进 server 角色日志)、`Shutdown()` 晚于 `loop.Stop()`(apply 线程正是发布 `srv`/`srvpark` 的线程) | -| `MOBILEGL_PIPE_STATS_FILE` 的角色分路 | `MobileGL/MG_Util/{Metrics/PipeStats.cpp,Debug/Log.{h,cpp}}` | 走 `RoleLogPath` **同一套导出规则**派生成 `.client.json` / `.server.json`;**基名不再指向任何文件**(没改的读取方会当场 `ENOENT`,这是刻意的)。G1 上这里红过三次(无条件派生 → `.text +24 B`;两臂共用一个局部 → 1 symbol resized;重写 `MGLOG_W` 格式串 → `.rodata +64 B`),最终形态是 pull 臂的代码**逐语句原样**、所有新增都在 `#if` 之内 | -| 测试 | `MobileGL/MG_Test/Util/PipeStatsTest.cpp` 等 | PipeStats **25/25**;全量 unit **2367/2367**;spawn integration **102/102**;**G1 符号 0/0/0/0 且 `.text` 逐字节相同**(强于门要求的 `+0`),pull 构建零 `MG_Remote` 符号、零新计数器符号 | -| 设备 A/B 工具入库 | `tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}` | 采集(reboot / pin / 按序跑臂 / 逐线程 CPU 采样 / 按角色拉日志 / 臂的证明)与渲染(`session-summary.json` → 表格)分离,渲染器只读采集结果,不能影响采集;`spawn` 的两个结构性零(`srv` 与 `frames` 在 server 日志)由渲染器处理并**在表里标明取自哪条日志** | -| runner 的包名覆盖 | `tools/trace_replay/run_android_retrace_local.py`(+19 行) | `MOBILEGL_TRACE_PACKAGE`;一台机器上已有另一个 key 签的 trace 安装(两个不同 key 签的 APK 不能共用一个 id),所以门 7 / 门 8 的 APK 装成 `top.mobilegl.plugin.p6gate7.trace` / `.p6gate8.trace` 并与既有安装并存,runner 的包名常量由此覆盖;既有安装原样保留 | -| `pin_device.sh` 的 `2f7cbe2e` 行 | `tools/device_bench/pin_device.sh`(+28 行) | 定频路径 + kgsl 节点 + GPU 钳 1050 MHz 的实测值(§12.7);三个动作(`check` / `pin` / `unpin`)都实跑过,pin 在负载下保持、在 idle 下 90 s 稳定,stock 值是硬编码而非现场采样(否则会把已钉的状态读成 stock 并永久钉住) | - -### 12.7 不可比项与未跑项 - -- **本板 GPU 钳 1050 MHz。** `pin_device.sh` 的 `2f7cbe2e` 行把 kgsl `min/max_pwrlevel` 钉 0,但本板实际拿到的是 **1050000000 Hz(1050 MHz)**:`thermal_pwrlevel` 恒为 1(写 0 返回成功、读回仍是 1),`max_gpuclk` 只读且为 1050000000,devfreq 的 `max_freq` 也把 1100000000 的写夹下来。所以这一行记的是「**本板真正能跑到的最高频**」,不是顶端 OPP,`check` 对着真相比对。**后果**:本节的绝对时间与 **1100 MHz 时代**的 `35d0befa` 行(§3.2 / §4.4)**不可比**,与 §7.4 那一轮 Redmi 的 1100 MHz 口径也不可比——钟频不同,**只有同场配对可比**,本节的配对结论也只在这个意义下成立。 -- **OpenRA 真机辅负载未跑。** 门 7 / 门 8 的真机会话只跑 `minecraft-1.21.4-rd12-odinlite-in-world`,OpenRA 只在 WSL 桌面 llvmpipe 的 retrace 车道跑过(§12.4 的两个数之一)。**真机 OpenRA 未跑**,如实记录。 -- **门 8① 的两种门铃只在一条负载、一台设备上比过**,且参照注记里的 1600 spins/帧 属于更重的场景;若存在差异,应在每帧会合次数更多的负载上才看得见。 -- **spawn client 的窗口化字段仍不可信**,而且这一项是**结构**不是缺陷:要让 spawn client 推进窗口,得让 client 进程到达一次 `PipeStats::OnPresent`,而 client 的帧边界在 spawn 下是 `EmitPresent`(每条 present 记录一次)——接上去会与 backend 的帧边界重复计数。**不要顺手接**。 -- **`MOBILEGL_IPC_STAGE_MB` 同时是 arena 大小与 chunk 预算的来源**,所以分块的净效果没有被单变量隔离(§12.5 的 `tex` +194%)。 - -## 13. P3b/P4b wave 2-D 包 D2(`cf7ca59f`,WSL 桌面 lavapipe/llvmpipe) - -### 13.1 上传形状金标(R-11 / D-D4,`TextureUploadShapeScenario` 由记录升为门) - -工作负载常量 `kScatteredRects=40`、`kFrames=3`、一条连续带、**三张**纹理(第三张经 -`glTextureView` 的 level 1 上传,使 view/属主重映射落在被计数的窗口内)。 - -| 臂 | emit | box | rect | jobs | client emitter | -|---|---|---|---|---|---| -| `DirectGLES.TextureUploadShape.`(monolith) | 9 | 9 | 0 | 9 | 9 | -| `DirectGLES.Split.`(inproc) | 9 | 9 | 0 | 9 | 9 | -| `DirectGLES.Spawn.` | 9 | 9 | 0 | 9 | 9 | -| `DirectGLES.Tcp.` | 9 | 9 | 0 | 9 | 9 | - -P4a 的记录是两张纹理、无 client emitter 的 `emit=6 box=6 rect=0 jobs=6`(`ctu=0`),与本行 -**不可比**:工作负载不同。 - -`rect=0` 是 **unpack-ring 策略的既定输出**:client 确实建出 30 个 rect(2 texel 间隙, -`RegionsTouch` 不合并;summed area 120 远低于 union box 的 3/4,两道 client 侧谓词都不取 box 臂), -记录把 30 个 region 全部带过去;是 server 在 `Managers.cpp:8649` 的 -`if (UnpackRingAvailable()) dirtyRectCount = 0;` 决定取 box——"One box, one job",Mali 悬崖的既有 -缓解。门钉的就是这个策略的输出。 - -**散点图案本身是被修过的(评审 fable 指出)**:原来的 stride `x=((i*7)%32)*2` / `y=((i*5)%32)*2` -在 64 px 图集里会打到 x=62、y=62,union box 覆盖整个 level,而 server 的 `subRectEligible` -(`Managers.cpp:8486-8491`)要求 `!dirtyRegion.CoversWholeLevel`(`MipmapStorage.h:30-33`,**包围盒** -判据)——两张散点纹理在 ring 策略之前就已经 rect-ineligible,金标数字一样但量的不是同一件事。 -内缩一个 rect(`kScatterMargin`)后 union box 落在 (2,2)..(62,54),决策点才真正带电。 - -红一次因此是**零补丁**的:`MOBILEGL_ESPRYT_DISABLE_UNPACK_RING=1` 一个环境变量就把两张散点纹理 -从 box 翻成 rect——`box=3 rect=6 jobs=183`(3 = 连续带 x3 帧;6 = 两张散点 x3 帧;183 = 3 + 6x30), -`bytes/f[tex]` 同时从 104448 降到 9024,而 26 个像素用例全绿。内缩之前同一个变量是全绿的,这正是 -`CoversWholeLevel` 把决策点短路掉的证据。 - -### 13.2 client 半边的读法(本包的修正) - -`ctu=` 与 `tex[]` 是否在同一个窗口里,按传输分: - -| 臂 | `tex[]` 在哪 | `ctu=` 在哪 | -|---|---|---| -| monolith | 唯一日志 | 同一行 | -| inproc | **server** 日志(两个文件、一个进程、一套计数器) | 同一行 | -| spawn / tcp | **server** 日志 | client 进程自己的计数器,**Present 节奏不同**——实测 `ctu=13` 对 `emit=9`,且用例读取时通常还没写出 | - -这与 §12.7 最后一条"spawn client 的窗口化字段仍不可信(是结构不是缺陷)"是同一件事,从集成用例 -这一侧再次证实。因此断言改读**进程内的 `MGPipeTextureEmitter::SubDataCount()`**(同一个计数器的源 -头,测试进程在四个臂下都是 client),`ctu=` 只在窗口对齐时断言;是否对齐由 -`PeekSplitRuntime()` 回答的传输决定,**不是**由数值是否 >= 0 决定——spawn 下 server 会发布一个 -完全可读的 `ctu=0`,按数值判会把比较悄悄变成 `0 == 9`。 - -### 13.3 未跑项 / 不可调度项 - -- **Mali 帧时增量(D-D4 要求与金标并列)不可调度。** 本阶段只有一台 Redmi(Adreno 830v2), - **无任何 Mali 设备**。按 Adreno 记录形状,偏差在此与场景头部注明:**Adreno-only; Mali delta - deferred**。不建模、不从 2026 年那次测量的条件推算。形状是悬崖的**因**,帧时是它在某一家硬件 - 上的**果**——门守的是因。 +全文:[`notes/p12/README.md`](notes/p12/README.md)、[`notes/p12/PLAN-P12.md`](notes/p12/PLAN-P12.md) §2。 diff --git a/docs/Disaggregated/README.md b/docs/Disaggregated/README.md index 280bee3c..18fc6751 100644 --- a/docs/Disaggregated/README.md +++ b/docs/Disaggregated/README.md @@ -1,12 +1,12 @@ # MGPipe:MobileGL 前后端拆分 -> 状态:**P0–P5e 已收官**(P5e 2026-09-19 收官,附一条具名未决:E1 对照需按 ID-114 重新定义),**当前在 P5f**(一切状态上 wire,计划 [`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md);P6 阻塞于它),P6 计划 [`P6-SPAWN-PLAN.md`](P6-SPAWN-PLAN.md)、契约草稿 [`P6-CONTRACT-DRAFT.md`](P6-CONTRACT-DRAFT.md),均尚未开工)。P5e:run-ahead 已武装(十二包落地,strict 车道硬绿 179/179、`integration-gpu` 1357/1357、pull/push/split 三个构建 flavour 全绿;设备上渲染距离 32 下 `inproc` 已与 `monolith` 齐平,相对它取代的 lockstep p50 +69%。报告 [`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md))(P5c 2026-09-17 `b88e8487`;P5d 三轮 2026-09-18,代码头 `1f8de61b`——`inproc` 性能专项,VD12 Minecraft 上 split 从 7-13 fps 拉到 103-106 fps、client 线程 CPU/帧从 15 ms 到 9.2 ms,报告 [`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md))。目标负载(四条 Minecraft A/B trace)已在 Redmi Adreno 830 上以 `inproc`(独立 apply 线程)双后端渲染,barrier tax 首次实测;P5c 把审计出的 59 处不经 wire 的直接内存访问全部归零(契约 `MobileGL/MG_Remote/CONTRACT-P5C.md`),`inproc` 成为只经 wire 交换的诚实两角色。**P6 只是传输替换**——但这句话的证据是 P5c 时代的,P6 的第一个包是重新证明它的只读审计。当前头、逐门数字与开放项见 [`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md)。 +> **状态(2026-09-24)**:P0 … P7 已收官;P6.5 第一波已落地;**当前阶段 P12**「server 自有屏幕窗口」子集,已实现、未收官——[`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md)。阶段表见 [`ROADMAP.md`](ROADMAP.md)。 > -> 性能纪律(2026-09-08 起):逐线程 CPU 与 tracker 绝对 ns **对着 pull 臂基线记录**,不作阻塞门;专门的优化阶段排在路线图推完之后。 +> 性能纪律(2026-09-08 起):逐线程 CPU 对着 pull 臂**只记录**、不作阻塞门;专门的优化阶段排在路线图推完之后。 ## 是什么 -MGPipe 是 MobileGL 前端(`MG_State` + `MG_Impl`)与后端(`MG_Backend`:Espryt = DirectGLES、Magma = DirectVulkan)之间的一份**显式接口**:gallium 形状、句柄寻址、只推不拉。它取代后端每 draw 直接读 `MG_State::pGLContext` 的做法,让后端拥有自己的状态机,并在此之上把前后端拆到**两个线程**(`inproc`,已达成)与**两个进程**(`spawn`,P6)。 +MGPipe 是 MobileGL 前端(`MG_State` + `MG_Impl`)与后端(`MG_Backend`:Espryt = DirectGLES、Magma = DirectVulkan)之间的一份**显式接口**:gallium 形状、句柄寻址、只推不拉。它取代后端每 draw 直接读 `MG_State::pGLContext` 的做法,让后端拥有自己的状态机,并在此之上把前后端拆到**两个线程**(`inproc`)、**两个进程**(`spawn`),乃至经 TCP 的**两台机器**。 接口本身是可独立交付的产物:即使 IPC 永不上线,`inproc` 就是 monolith 的渲染线程。 @@ -16,74 +16,63 @@ MGPipe 是 MobileGL 前端(`MG_State` + `MG_Impl`)与后端(`MG_Backend` 应用 GL 调用 → MG_Impl(GL 语义、错误、shadow) → MG_Impl/Pipe/Tracker:在每条 verb 之前 validate,把变化推成 MGPipe 调用 - → MGPipeScreen / MGPipeContext(两张函数指针表,79 条调用,单一真相源 PipeCalls.def) - monolith:直调 backend 函数 split:发射器写 SEG_CMD ring → server applier(apply 线程) - → server 对象表(按 {slot, gen} 句柄索引的数组)+ PipeInputs(后端被推送的状态块) - → MG_Backend(Espryt / Magma),两个后端的 ring / pool / memo / lowering pass 原样不动 - ← MGPipeCallbacks(10 个具名反向回调 + 1 个正向终止符) + → MGPipeScreen / MGPipeContext(两张函数指针表,单一真相源 PipeCalls.def) + monolith:直调后端 split:发射器写记录 → 数据面(共享段 / 字节流)→ server 的 applier(apply 线程) + → server 对象表(按 {slot, gen} 句柄索引)+ PipeInputs(后端被推送的状态块) + → MG_Backend(Espryt / Magma),两个后端的 ring / pool / memo / lowering 原样不动 + ← MGPipeCallbacks(9 个具名反向回调 + 1 个正向终止符,split 下走 SEG_EVENT) ``` -三种形态共用**同一份 backend 实现**:`monolith`(默认,进程内直调)、`inproc`(同进程两个线程,CI 形态与渲染线程交付物;P5 起 lockstep verb barrier,P5b 起 71 个后端槽里 54 个发射、15 个具名拒绝)、`spawn`(P6:`fork`+`execve` 出 server 进程,SPSC 共享内存 ring + FlatBuffers 控制面)。 +三种拓扑共用**同一份后端实现**:`monolith`(默认)、`inproc`(同进程两线程,CI 主验收;client 在 draw 路径上 run-ahead——Espryt 自 P5e、Magma 自 P5f 之后)、`spawn`(两进程;控制面 `fork` / `unix:` / `tcp://` × 数据面共享段 / 字节流,可跨机)。设计全文见 [`ARCHITECTURE.md`](ARCHITECTURE.md)。 ## 构建与运行 -四个 flavour:`build-linux`(pull,默认)、`build-push`(`-DMOBILEGL_PIPE_PUSH=ON`)、`build-verify`(`+MOBILEGL_PIPE_VERIFY` 影子比对,`MOBILEGL_ITEST_REQUIRE_GPU=1`)、`build-split`(`+MOBILEGL_BUILD_DISAGGREGATED +MOBILEGL_BUILD_DISAGGREGATED_INPROC`)。split 运行时必须显式设置传输: +四个 flavour:`build-linux`(pull,默认)、`build-push`(`-DMOBILEGL_PIPE_PUSH=ON`)、`build-verify`(+`MOBILEGL_PIPE_VERIFY`,影子比对)、`build-split`(+`MOBILEGL_BUILD_DISAGGREGATED` +`MOBILEGL_BUILD_DISAGGREGATED_INPROC`)。split 构建必须显式选拓扑,不设时是 monolith 对照臂: ```text MOBILEGL_TRANSPORT=inproc MOBILEGL_ITEST_REQUIRE_GPU=1 ctest --test-dir build-split -L integration-split --output-on-failure -MOBILEGL_TRANSPORT=inproc ctest --test-dir build-split -L integration-gpu --output-on-failure # 普查车道,只记录 +ctest --test-dir build-split -L 'integration-(spawn|tcp)' --output-on-failure # 条目自带 spawn / tcp 环境与私有日志 ``` -`integration-split`(`DirectGLES.Split.*` / `DirectVulkan.Split.*`)是硬门;`integration-gpu` 在 inproc 下是带已知 abort / wrong-answer 的普查车道。每条 split entry 带独立 `MOBILEGL_LOG_FILE_PATH`,阴性控制(`scripts/ci/split_negative_controls.sh`)只读被选 entry 的私有日志。常用旋钮(全表见 `ARCHITECTURE.md` 附 A): +常用旋钮(全表见 [`ARCHITECTURE.md`](ARCHITECTURE.md) 附 A):`MOBILEGL_TRANSPORT`(拓扑)、`MOBILEGL_IPC_CONTROL` / `MOBILEGL_IPC_DATA`(控制面 / 数据面)、`MOBILEGL_IPC_TOKEN`(非 loopback 必需)、`MOBILEGL_IPC_RUN_AHEAD`、`MOBILEGL_IPC_STAGE_MB`(默认 32,1/4 是内容分块预算)、`MOBILEGL_IPC_SURFACE=server`(使用 server 自有窗口,P12)、`MOBILEGL_PIPE_STATS=1`(边界计数器)。 -| 变量 | 默认 | 用途 | -|---|---:|---| -| `MOBILEGL_IPC_STAGE_MB` | `32` | `SEG_STAGE` 容量(一条记录一个整 blob);**内容分块预算 = 本值的 1/4**(`MGPipeStageChunkBytes()`,默认 8 MiB),超预算的 buffer 范围 / 纹理级切成多条记录,故历史 profile 的显式 `256` 不再是这两条路径的必需 | -| `MOBILEGL_IPC_VERB_BARRIER` | `1` | 每个 verb 等 `appliedSeq == emitSeq`;`0` 只作 E1 阴性控制 | -| `MOBILEGL_IPC_AUDIT` | `0` | apply 返回后以 `0xDD` 填退休 staging,查跨返回持针 | -| `MOBILEGL_IPC_STRICT_ERRORS` | `0` | BARRIER-PULLED residual input 提升为具名 Fatal | -| `MOBILEGL_IPC_ADOPT_TIER` | `2` | split 使用 emulated persistent-map 路径;`auto/0/1/2` | -| `MOBILEGL_IPC_PERSISTENT_BLOCK_KB` | `64` | persistent-map 块推送粒度;`0` 是 E3(a) 阴性控制 | - -Android 三份 APK flavour:pull、push、split(Gradle 属性 `mobilegl.pipePush`、`mobilegl.buildDisaggregated`、`mobilegl.buildDisaggregatedInproc`;`~/w7/notes/tools/wsl_build_p5_apks.sh`)。split APK 需运行环境 `MOBILEGL_TRANSPORT=inproc`,不设置时是 split build 的 monolith control arm(splitctl)。设备 A/B 只用 Redmi `2f7cbe2e`,定频协议见 `devices/pin-verification-2026-09-07.md`。 +Android:三份 APK flavour(pull / push / split,Gradle 属性 `mobilegl.pipePush`、`mobilegl.buildDisaggregated`、`mobilegl.buildDisaggregatedInproc`)。设备侧 server 有离屏的前台 Service 与上屏的 `MobileGLDisplayActivity` 两种形态。设备 A/B 只用 Redmi `2f7cbe2e`,定频协议见 [`devices/pin-verification-2026-09-07.md`](devices/pin-verification-2026-09-07.md)。 ## 文件地图 | 文件 | 内容 | |---|---| -| `CURRENT_STAGE_PROGRESS.md` | 当前头实测、P5b 落地内容、开放项、下一步、证据位置、仍在生效的裁定;随每次落地更新 | -| `ARCHITECTURE.md` | 已定稿的设计:句柄与世代、调用目录与生成器、记录约定、tracker、纹理路径、shader 制品、反向通道、后端改造、传输、persistent map 分档、进程 / EGL / 平台、构建与门、P5 / P5b 落地形状、开关表 | -| `ROADMAP.md` | 纪律、两条跑道、P0…P13 阶段表、里程碑、债务表、开放问题 | -| `P5F-WIRE-COMPLETENESS.md` | P5f 计划:`gPipeInputs` 是什么、拆进程后它怎样;阻碍跨进程的完整清单(`BARRIER_PULLED` 15 字段、Magma、EGL 控制面、静态量、豁免 scope、反向通道);双块演练机制与出口门 | -| `P6-SPAWN-PLAN.md` | P6 的包计划:树上已有什么(传输原语基本已就绪且有测试)、真正要造什么、P6 与 P12 的边界、七个包与依赖序、出口门与阴性对照 | -| `P6-CONTRACT-DRAFT.md` | P6 契约草稿(规则 G、令牌、控制面帧、死亡与 run-ahead、阶段修正);`c6` 落地时移为 `MobileGL/MG_Remote/CONTRACT-P6.md` | -| `MEASUREMENTS.md` | 逐阶段实测:P0 spike 与基线、P1 verify、P2 门与 DriverBench、P3a / P4a 门与设备 A/B、P5 全门 / E1–E6 / Redmi 四臂、P5b 主机门 / 普查 / 审查 / Redmi 出口 | -| `devices/pin-verification-2026-09-07.md` | 设备定频档案与核验(Redmi 当前口径;小米 / Oppo 历史核验) | +| [`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md) | 当前阶段:落地了什么、门、下一步、阻塞(摘要;细节链到 `notes/<当前阶段>/`) | +| [`ROADMAP.md`](ROADMAP.md) | 纪律、两条跑道、阶段表(每阶段一行摘要 + 细节链接)、里程碑、开放的债务与问题 | +| [`ARCHITECTURE.md`](ARCHITECTURE.md) | 已定稿的设计(§1–§16)、阶段落地形状索引(§17)、开关(附 A)与计数器(附 B) | +| [`MEASUREMENTS.md`](MEASUREMENTS.md) | 每阶段的头条实测数字,全文在各阶段 notes | +| [`notes/`](notes/README.md) | **阶段细节**:每阶段一个目录,`README.md` 是该阶段的汇总(阶段表原行、逐门数字、落地形状),其余是计划、契约草稿、裁定、审计、报告与证据 | +| [`devices/`](devices/pin-verification-2026-09-07.md) | 设备定频档案与核验(跨阶段共用) | 代码地图: | 路径 | 作用 | |---|---| -| `MobileGL/MG_Pipe/` | `PipeCalls.def`(目录)、`PipeFields.def`、`Coverage.def`、`FieldOwnership.def`、`FillPoints.def`、`DirtySurface.def`;`MGPipeTypes.h`(payload POD)、`MGPipeValueTypes.h`、`MGPipeHandles.h`、`MGPipeHostSpan.h`、`MGPipeCallbacks.h`、`MGPipeRenderStateSpans`、`PipeApply`、`PipeRoute`;`generated/*.inc`(G1–G8 产物,提交进树) | -| `MobileGL/MG_Impl/Pipe/` | `Tracker.h`、`PipeFill`、`SlotAllocator`、`CsoCache`、`CompositeResolver`、`ResourceTracker`、`SetHashSuppressor`、各族 `*Emit.h` | -| `MobileGL/MG_Backend/MGPipe/` | `PipeInputs.{h,cpp}`(后端被推送的状态块,G5 / G8 产物的 include 点) | -| `MobileGL/MG_Remote/` | 仅 `MOBILEGL_BUILD_DISAGGREGATED=ON`:`Protocol/`(`protocol.fbs`)、`Transport/`(`Ring`、`Doorbell`、`ShmSegment`、`FdPassing`、`Framing`、`InProcessTransport`、`ReplySlot`、`EventRing`)、`Wire/PipeWireCodec`(记录编解码、`WireVerbSink`)、`Client/`(`BackendObject_Remote`、`ClientSession`、`EmitTables`、`CapsMirror`、`PersistentMapTracker`)、`Server/`(`PipeApplier` + `ServerVerbSink`、`ServerLoop`、`ServerSession`、`StagedShadow`);`CONTRACT-P5.md`、`CONTRACT-P5B.md` | -| `scripts/` | `gen_pipe.py`(G1–G7)、`gen_pipe_field_ownership.py`(G8)、`gen_pipe_dirty_surface.py`、`gen_protocol.py`、`symbol_report.py`(G1)、`check_include_closure.py`、`check_doc_citations.py`(本目录 `file:line` lint)、`p3a_/p4a_untouched_regions.sh`(G5)、三个阴性对照脚本、`ci/`(split 阴性控制、普查 JUnit、smoke) | +| `MobileGL/MG_Pipe/` | 调用目录与各 `.def` 表、payload / 句柄 / 回调头、`PipeApply`、`PipeRoute`、`generated/*.inc`(G1–G8 产物,提交进树) | +| `MobileGL/MG_Impl/Pipe/` | Tracker、`PipeFill`、`SlotAllocator`、`CsoCache`、`CompositeResolver`、`ResourceTracker`、各族 `*Emit.h` | +| `MobileGL/MG_Backend/MGPipe/` | `PipeInputs`(后端被推送的状态块) | +| `MobileGL/MG_Remote/` | 仅 `MOBILEGL_BUILD_DISAGGREGATED`:`Protocol/`、`Transport/`(`ITransport` / `ILink` 两轴)、`Wire/`、`Client/`、`Server/`;各阶段 wire 契约 `CONTRACT-*.md` | +| `scripts/` | `gen_pipe.py`(G1–G7)、`gen_pipe_field_ownership.py`(G8)、`gen_pipe_dirty_surface.py`、`gen_protocol.py`、`symbol_report.py`(G1)、`check_include_closure.py`、`check_doc_citations.py`(本目录 `file:line` lint)、`link_ratchet.py`、G5 保护区脚本、`ci/` | | `MobileGL/MG_Util/Metrics/PipeStats.{h,cpp}` | 边界计数器,`MOBILEGL_PIPE_STATS=1` 开启 | -| `MobileGL/Config.h`、`MobileGL/ConfigLoader.cpp` | `MOBILEGL_PIPE_*`、`MOBILEGL_TRANSPORT`、`MOBILEGL_IPC_*` | -| `MobileGL/MG_Test/Pipe/`、`MG_Test/Wire/`、`MG_IntegrationTest/Harness/Pipe*Peek` | 目录 / tracker / emitter 单元、wire 层与 remote client/server 单元、白盒断言 | -| `tools/spikes/`、`android-plugin/app/src/trace/cpp/spawn_spike.cpp` | spike A(Android exec 第二个原生可执行文件)、spike B(跨进程外部内存分档) | +| `MobileGL/MG_Test/Pipe/`、`MG_Test/Wire/`、`MG_IntegrationTest/Harness/*Peek` | 目录 / tracker / emitter 单元、wire 与 remote 单元、白盒断言 | +| `tools/trace_replay/`、`tools/device_bench/` | retrace 跑器(含 TCP 矩阵)、设备定频与配对 A/B 工具 | ## 术语 -- **client / server**:前端进程 / 后端进程;monolith 下是同一进程的两个角色,inproc 下是两个线程。 -- **verb**:会让 server 做事的命令(draw、dispatch、clear、blit、readback、XFB 跨度、query、纹理操作)。推送只发生在 verb 之前的 validate 时刻。 -- **class A / B / C**:后端槽的三类——A 从 caps mirror 本地回答,B 发射记录,C 具名拒绝(`Fatal{UnmigratedVerb}`),永不回落到 monolith applier。 +- **client / server**:前端角色 / 后端角色;`inproc` 下是两个线程,`spawn` 下是两个进程(可在两台机器上)。 +- **verb**:会让 server 做事的命令(draw、dispatch、clear、blit、readback、XFB 跨度、query、纹理操作);推送只发生在 verb 之前的 validate 时刻。 +- **class A / B / C**:后端槽的三类——A 从 caps mirror 本地回答,B 发射记录,C 具名拒绝(`Fatal{UnmigratedVerb}`),永不回落到 monolith。 - **CSO**:常量状态对象(render state、vertex elements、sampler、sampler view、shader),client 侧内容寻址,server 侧按句柄缓存。 - **Track V / Track H**:值类读点的迁移(整块 POD 过线)/ 对象类读点的迁移(`SharedPtr<前端对象>` → 句柄)。 +- **lockstep / run-ahead**:每条 verb 等 server 应用完 / client 发布完即走,只有 barriered 记录才等(P5e)。 - **`MGGen`**:server 私有的"我重铸了驱动对象"纪元,永不过线;与句柄里的 client 世代严格分开。 -- **G1 / G2 / G5 / G14**:pull 符号恒等 / pull==push 测试名 / 保护区字节一致 / 测试名只增不删。**barrier tax**:split − push 的逐线程 CPU 增量(inproc 传输 + verb barrier 的代价)。 +- **G1 / G2 / G5 / G14**:pull 符号恒等 / pull == push 测试名 / 保护区字节一致 / 测试名只增不删。**barrier tax**:split − push 的逐线程 CPU 增量。 ## 历史 -本目录此前是一份 328 KB 的实施计划(`PLAN.md`)加 135 KB 的设计竞赛与评审记录(`REVIEW.md`);设计定稿后只保留设计与架构本身,评审记录与早期草案留在 git 历史(`8b31de2f`、`1794ac94`、`8349babe`、`87ee17c6`)。P5b 收官后(`ea34bccb` 及之前)各文档的完整历史叙述、-O0 设备表与逐轮审查记录同样以 git 历史为准;集成者的逐条裁定长文(ID-1..75)在 `ef35ea0c` 之前版本的 `CURRENT_STAGE_PROGRESS.md`。 +本目录最早是一份 328 KB 的实施计划加 135 KB 的设计竞赛与评审记录;设计定稿后只保留设计与架构,评审与早期草案在 git 历史(`8b31de2f`、`1794ac94`、`8349babe`、`87ee17c6`),以及 `notes/recovered/` 里捞回的独有稿。2026-09-24 再次整理:四份索引文档只留摘要,各阶段细节(阶段表原行、逐门数字、落地形状、当时的进度页)移入 `notes/<阶段>/README.md`,散在本层的阶段报告与计划(P5d / P5e / P5f / P6)移入各自阶段目录;整理前的版本见 `1fb18d9`。 diff --git a/docs/Disaggregated/ROADMAP.md b/docs/Disaggregated/ROADMAP.md index 8c0a4ea8..aaa59b4d 100644 --- a/docs/Disaggregated/ROADMAP.md +++ b/docs/Disaggregated/ROADMAP.md @@ -1,282 +1,107 @@ # MGPipe 路线图 -> 状态:**P0、P0.5、P1、P2、P3a、P4a、P5、P5b、P5c、P5d、P5e、P5f 已收官**。P5f 于 2026-09-20 完成主机门、Redmi 六个 clean-boot 臂、跨模型族审查及修复验证;最终验收代码为 WSL `cfca93c7885fd8db1e881f91189ba59090ae9c52`(Windows `c42577a4` 等价)。证据见 [`notes/p5f/close-report.md`](notes/p5f/close-report.md)、[`close-review.md`](notes/p5f/close-review.md)、[`device-report.md`](notes/p5f/device-report.md)。**P6 已收官(2026-09-22)**:全部包(a6 / c6 / lk / so / sm / cp / hs / dl / st / t6)已落地于 `feat/disaggregated`(头 `77cbd176`,2026-09-21),出口门第 1–6 项此前已达成;**第 7 项**(Redmi `2f7cbe2e` 的 reboot-clean 同热窗口配对 A/B)与**第 8 项**(三个强制性能数)已于 2026-09-22 采齐——门 7 判读为 **tie**(inproc vs spawn 的 client CPU p50 差 −0.15% / +0.16%,均在臂内散布 0.2–4.2% 之内,符号随臂序翻转;monolith 基线约贵 30%),门 8 三数齐:门铃 A/B tie(`clipark` 略差)、`SEG_STAGE` 字节/帧、分块后记录/帧。证据 [`notes/p6/gate7-device-ab.md`](notes/p6/gate7-device-ab.md)、[`notes/p6/gate8-doorbell-device.md`](notes/p6/gate8-doorbell-device.md)、[`notes/p6/gate8-wire-counters.md`](notes/p6/gate8-wire-counters.md)。逐包状态、出口门逐项、证据见 [`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md);设计见 `ARCHITECTURE.md`。P5e 的历史性能与 E1 对照债保留在阶段行、[`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md) 和 `MEASUREMENTS.md`,不由 P5f 的新对照代为关闭。**CI / inproc 功能补齐已收官(2026-09-21)**:拆分主产物 + inproc 主验收、完整 retrace 双 transport、18 项 CI 门、双 ABI APK / AVD 全绿并合入 `feat/disaggregated`(`e1bf3677`),(原记 `CURRENT_STAGE_PROGRESS.md` §2.8;该文档已为 P6.5 重写,原文在 git 历史)。**2026-09-22 起进入 Ph / P3b-P4b / P7 统筹推进**:三行逐项审计、判定与波次见 [`notes/p7/PLAN-PH-P34B-P7.md`](notes/p7/PLAN-PH-P34B-P7.md),裁定见 [`notes/p7/INTEGRATOR-DECISIONS-P7.md`](notes/p7/INTEGRATOR-DECISIONS-P7.md)。 +> **状态(2026-09-24)**:P0 … P7 已收官(含 P5c–P5f 与 P6);P6.5 第一波已落地(残余交 CI / 后续);Ph 的必需小件随 P7 落地;P3b/P4b 余项并行。**当前阶段是 P12**「server 自有屏幕窗口」子集:已实现并入 `feat/disaggregated`,未收官——见 [`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md)。 +> +> 本页只留摘要。每个阶段的完整阶段表行、逐门数字、设计与报告在 `notes/<阶段>/README.md`(下表"细节"列);设计见 [`ARCHITECTURE.md`](ARCHITECTURE.md),实测摘要见 [`MEASUREMENTS.md`](MEASUREMENTS.md)。 -> **P5f 后续 Magma run-ahead 已完成**(`194382c9`,2026-09-20):主机真实排队、credit、负控及 Vulkan 同步验证通过,Redmi 同包 Magma 开/关与 GLES 三臂 FCL 世界实测通过。见 [报告](notes/p5f/magma-runahead.md)。P6 / P7 全阶段状态不因此改变。 +## 终局 -> **终局(2026-09-22 改判)**:client 与 server 可以在**不同机器、不同 OS / 架构**上,经 **TCP** 连接;同机的 `spawn`(AF_UNIX + 共享段)保留为本机形态。传输栈拆成**两根独立可选的轴**——控制面(`ITransport`:`fork` 继承 fd / `unix:` / `tcp://host:port`)与数据面(`ILink`:共享段 `ShmLink` / 字节流 `StreamLink`)——由握手协商、可以混搭(同机可以控制面走 TCP、数据面走共享段以降开销),上层只见 `ITransport` + `ILink`,**不得按链路种类分支**。此前 [`P6-ENDSTATE-REVIEW.md`](P6-ENDSTATE-REVIEW.md) 假设的 AVF pVM / `AF_VSOCK` 终局撤回(该文顶部有更正)。这一改判把 **P6.5 从条件项变成 IPC 跑道的关键路径**,把 wire 定宽 + 布局摘要从 P7 / hs 的"以后"变成 P6.5 的第一个包,把 Ph(含配对 / 认证)变成 P12 非 loopback 监听的前提;设计见 `ARCHITECTURE.md` §11.9。 +client 与 server 可以在**不同机器、不同 OS / 架构**上经 **TCP** 连接;同机的 `spawn`(AF_UNIX + 共享段)保留为本机形态。传输栈是控制面(`ITransport`)× 数据面(`ILink`)两根独立可选、握手协商、可混搭的轴,上层不按链路种类分支([`ARCHITECTURE.md`](ARCHITECTURE.md) §11.9)。这一改判(2026-09-22,取代此前 AVF pVM / `AF_VSOCK` 的设想)让 P6.5 成为 IPC 跑道的关键路径,并让 Ph(不可信对端加固)成为非 loopback 监听的前提。 ## 通用纪律(每个 commit) - 默认 ALL target 必须完整构建;禁止提交热路径插桩(CI grep 门)。 -- **每个门必须能因它存在的理由变红**:每个门带阴性对照并真跑过一次红(R-16);公共 GL 看不见的改白盒断言。 -- 正确性门是硬门;**性能对着 pull 臂只记录、不作阻塞门**(用户 2026-09-08:push 比 pull 多约 10% 逐线程 CPU 已被接受;专门的优化阶段排在路线图推完之后)。唯一不可谈判的是数字必须真的采到。 -- 每阶段出口跑一次五部分门(`ARCHITECTURE.md` §13.2);性能判据是**逐线程 CPU 时间**;设备对比走 reboot-clean + 同热窗口配对 A/B,只用 Redmi `2f7cbe2e`(`devices/pin-verification-2026-09-07.md`);Windows 机器不是正确性门。 -- G1:pull 构建的符号集与 `.text` 字节对基线恒等(每阶段 0/0/0/0);G2:pull 与 push 的 ctest 名集合相同;G14:测试名只增不删;G5:保护区字节一致。 -- **拆分不借机顺手修 `dev` 的 bug**:句柄化过程中发现的 `dev` 侧缺陷记成独立条目、独立 PR(开放问题 15、17)。 -- 过程(用户 2026-09-16,ID-66):不过度验证;轮次间不做对抗性审查,每个 P 阶段收官由 Codex 审一次,发现进入下一阶段首轮;只读 / 非核心任务派给 Codex;实现方与审查方错开模型族。 +- **每个门必须能因它存在的理由变红**:带阴性对照并真跑过一次红(R-16);公共 GL 看不见的改白盒断言。 +- 正确性门是硬门;**性能对着 pull 臂只记录、不作阻塞门**(用户 2026-09-08),但数字必须真的采到。专门的优化阶段排在路线图推完之后。 +- 每阶段出口跑一次五部分门([`ARCHITECTURE.md`](ARCHITECTURE.md) §13.2);性能判据是**逐线程 CPU 时间**;设备对比走 reboot-clean + 同热窗口配对 A/B,只用 Redmi `2f7cbe2e`([`devices/pin-verification-2026-09-07.md`](devices/pin-verification-2026-09-07.md))。 +- G1:pull 构建的符号集与 `.text` 对基线恒等(每阶段 0/0/0/0);G2:pull 与 push 的 ctest 名集合相同;G14:测试名只增不删;G5:保护区字节一致。 +- **拆分不借机顺手修 `dev` 的 bug**:发现的 `dev` 侧缺陷记成独立条目、独立 PR。 +- 过程(用户 2026-09-16,ID-66):不过度验证;每个阶段收官由异模型族审一次,发现进入下一阶段首轮;实现方与审查方错开模型族。 -两条跑道分开:**monolith 跑道** P0 → P0.5 → P1 → P2 → P3a → P4a → P3b/P4b → P7 → P8 → P13,每段可独立交付、可随时中止;**IPC 跑道** P5 → P5b → P5c → P5d → P5e → **P5f** → P6 → **P6.5 → Ph → P12** → P9 → P10 → P11(同机臂)。目标顺序:先完整的 separate-thread rendering(P5b,已达成),再让 `inproc` 变成只经 wire 交换的诚实两角色(P5c),再把它的 CPU 代价压到可接受(P5d),然后退役 draw path 上的 lockstep(P5e:client 发了就走,server 只从记录带来的句柄解析对象),然后是 separate-process transport(P6,此时只是传输替换)。P6 之后(2026-09-22 定):传输栈两轴化并能跨机(P6.5),server 能安全地接受不是它自己产生的连接(Ph),server 成为拥有显示的常驻应用(P12),异步反向通道与节奏(P9、P10)建立在 P6.5 的消息式 reply 之上,同机的 ≥16 MiB 采纳(P11)最后。 +## 两条跑道 + +- **monolith 跑道**:P0 → P0.5 → P1 → P2 → P3a → P4a → P3b/P4b → P7 → P8 → P13。每段可独立交付、可随时中止;接口本身即使 IPC 永不上线也有价值。 +- **IPC 跑道**:P5 → P5b → P5c → P5d → P5e → P5f → P6 → P6.5 → Ph → **P12** → P9 → P10 → P11。顺序的理由:先有完整的独立渲染线程(P5b),再让 `inproc` 成为只经 wire 交换的诚实两角色(P5c / P5f),把 CPU 代价压到可接受(P5d)并退役 draw path 的 lockstep(P5e),此时 P6 才只是传输替换;之后传输栈两轴化并跨机(P6.5)、能安全接受外来连接(Ph)、server 成为拥有显示的常驻应用(P12);异步反向通道与节奏(P9、P10)建在 P6.5 的消息式 reply 之上;同机 ≥16 MiB 采纳(P11)最后。 ## 阶段 -| 阶段 | 状态 | 落地什么 | 验收门 / 证据 | +| 阶段 | 状态 | 摘要 | 细节 | |---|---|---|---| -| **P0** 卫生、度量、门、骨架 | ✅ | 边界计数器;`PipeCalls.def` 完整目录 + payload POD + 生成器 G1–G7 + CI `pipe-gates`;`gen_pipe_dirty_surface.py`;`check_doc_citations.py`;`MOBILEGL_PIPE_*` 开关;`MG_Remote/{Protocol,Transport}` 骨架 + `protocol.fbs` + `flatc-check` + `MG_Test/Wire`;三个严格 no-op 收益;spike A / B;retrace `--env` 透传 | 单元 / 集成 / trace 逐名不变;wire 层测试绿;两台设备字节 / 调用基线在案;spike 结论。`MEASUREMENTS.md` §1 | -| **P0.5** 值头与制品头抽取 | ✅ `5d99ee43` | `MG_Pipe/MGPipeValueTypes.h`;`ProgramArtifacts.h`;`Visit()` 归档 + `sizeof` 绊线;CI `-H` include 闭包断言(`scripts/check_include_closure.py`)+ `scripts/symbol_report.py` | 测试名零删除、`.text` 不变、符号 0 增 / 0 删 / 42 重命名 | -| **P1** `PipeInputs` 替换与 verify harness | ✅ | `MG_Backend/MGPipe/PipeInputs.h`(63 字段);277 处箭头 + 58 行非箭头逐条转换;逐 verb 类填充点;逐 verb 世代 poison;G4 影子比对器 + verify CI 模式;push-on-mutation 三字段 | pull `nm` 不变;单元 1485×3;`integration-gpu` 878;`integration-verify` 818 零 Fatal;79 retrace verify 79/79 armed 零分歧。§2 | -| **P2** 渲染状态 CSO + 第一片 Track H + 残余值块 | ✅ `738b289d` | Tracker(dirty 位、5 个聚合世代、抑制器);dirty-surface 成门;`MGPipeRenderStateSpans` chunk 表 + G7;`CsoCache`(64);`create/bind_render_state` + `set_dynamic_state`(`SyncRenderState` 一行不动);pixel pack / patch / attrib defaults;`ResidualValueBlock` 棘轮 1248 → 8;Espryt slot 表 + Magma vertex input 重键(11 条 memo 删除 + 2 条重键);`MOBILEGL_PIPE_LEGACY_MEMOS`;三个 capability 真存储 | G1 4 处认定 resize、`.text` +160 B;G2 名差 0;单元 1566×3;`integration-gpu` 916 三臂;retrace 79/79 push + verify;对照 44/44。**GO/NO-GO(2026-09-08):继续**。§3 | -| **P3a** handle wave 1(Espryt):buffer、VAO | ✅ `fde5fda3` | 九条 `resource_*` + 五条 vertex-input 调用接线到句柄形 `MGPipeResourceOps`;第七张 Espryt slot 表;`ResourceRespecify` 的 `kNeedsAck` 谓词;VAO twin 六条身份 memo 在句柄臂退役、四条重键;`mpr` 定义;位 7/8,push 默认 `0x1ff` | 五部分门全绿:G1 0/0/0/0 `.text +0`;G5 十一函数;单元 1619×3;`integration-gpu` 958 五臂;verify 842;retrace 79/79 ×2。实际 1 天。§4 | -| **P4a** handle wave 2(Espryt):FBO / 纹理 / sampler / program | ✅ `8c458cd5` | 十四条族调用接线(framebuffer state、sampler state / view、texture params、shader images、shader state、draw / dispatch program、global constants);纹理 / renderbuffer 复用 `resource_*`(目录不加行);六种 kind 按 `{slot, gen}` 重键;`CompositeResolver`;`Named = 3` framebuffer 记录;消费者门 + 客户端依赖表;位 9–12,push 默认 `0x1fff`;emulation 在 split 下具名 Fatal | 全门:G1 0/0/0/0;G5 17 区;G2 2902;单元 1785×3;`integration-gpu` 1117 七臂;verify 920;retrace 79/79;八族拒绝普查 0;`PipeApplyPeek` 白盒对照。实际 1 天。§5 | -| **P5** 传输 + inproc applier + 发射表 | ✅ `ff2994d9..37fc4fdb` | 同一 codec 上的 `InProcessTransport`;四种 build flavour;发射表三类 / caps mirror / reply mailbox;apply-thread context 终身持有;lockstep verb barrier;tight ReadPixels;persistent-map 块推送;G8 字段归属生成器;split 测试 / CI / APK 车道;八包(c0 契约、w1 codec、s1 session、p1 归属、b1 persistent map、t1 车道、c1 路由、v1 server)+ j0 / x2 / v1-r3 收尾 | joint:OpenRA inproc 2/2 SSIM 1.0;`integration-split` 21/21;E1 14/14 红;G1 0/0/0/0。收尾头全门在 E3(a) 停止(设计性 skip 被判红),Part 2/4 未到达;收官审查 1 blocker / 10 major / 2 minor → P5b r1/r2。§6 | -| **P5b** `inproc` verb migration | ✅ `37fc4fdb..82683d4a` | class-C 普查决定顺序;d1 19 / i1 7 / t2 6 / f1 11 槽迁移;sync 五槽;具名 blit;GLES mip descriptor;r1 / r2;收官审查三项修复。发射表 A=2 / B=54 / C=15 | 主机全门 `348d22a4` complete;`integration-split` 107/107;合并普查零回退、79 trace 72/6/1;**Redmi 正确性 8/8 + 四臂 A/B、barrier tax 首测**。§7 | -| **P5c** `inproc` 共享内存读点归零 | ✅ `11ac3de6..b88e8487` | server 端纹理 staged shadow(`StagedTextureStore`,句柄为键、整段覆盖、defined-ness 追踪);`SEG_EVENT` 三个 producer + `kEventGlError`(反向通道零裸指针);sink / twin 按记录句柄解析(blit 具名臂经 verb 句柄工作区,Magma 走 G6 消费臂);`applier_reset` / `object_death` 控制记录(opcode 77/78,framebuffer 首个 wire delete);`set_context_values`(79)退役全部值类 BARRIER-PULLED 行,三 shutter 自答;双层 `Fatal{RoleViolation}` 守卫 + `InBarrierWait` 接线;两个具名豁免 scope(通告家族 P4b/P7、G6 registry 家族 P3b/P4b)承接未到期的债 | 角色守卫开启下 unit 2187/2187(strict 下同绿)、`integration-split` 111/111、每层守卫 red-once 按名变红;纹理 `0xDD` audit 在 bsl / complementary 两条 in-world trace 零 Fatal;rsp 按帧实测(bsl 948.5/帧,残留全为钉住的对象类 15 行);G1 三个认定 resize(SwapchainObject::Create / CopyTexSubImage2D / ScopedRestartIndexSubstitution,均已具名)+ `.text` −16 B、pull 构建零 MG_Remote 符号;G5 保护区字节一致;双生成器 --check/--self-test 绿 | P5b | -| **P5d** `inproc` 性能专项 | ✅ `cb06538c`、`56a77348`、`1f8de61b` | 一二轮:persistent-map 推送三级臂(mprotect 脏页追踪 + 哈希抑制 + 绑定过滤 `PushDrawConsumers`)、barrier 批处理 `MOBILEGL_IPC_BATCH_WAITS` + 延期销毁队列、自旋时钟 64 合 1、零页注册 + shadow 页对齐分配。三轮(按符号化 profile 逐项切,四包 + 一份 lockstep 可行性研究):apply 空转轮询零锁(控制邮箱原子影子位)、`Doorbell::Wait` 校准自旋稳态不读时钟、wait/park 计数进 stats;`OnApplyThread()` 内联为线程指针比较、三个 scope 深度计数器退掉 `thread_local`;shadow 分配尺寸补页、追踪范围向外对齐、边块哈希消失、脏页排空修复;image unit 高水位、残余填充 memo、server stamp 常量表;`generate_mipmap` 不再免等(apply 读残余输入)、death 测试围栏 | 三轮设备(Redmi,CPU 定频):inproc **64.3 → 103-106 fps**(p50),client 线程 CPU 15.0 → 9.2 ms/帧、apply 12.4 → 7.0-7.4;monolith 同场景 115(120 Hz vsync 封顶的运行)/ 206(一次未封顶,渲染线程 5.0 ms/帧 大核);配对比较以逐线程 CPU/帧为主,见报告"数字"节;unit 2203/2203、`integration-split` 111/111(两条曾 flaky 的用例复跑 6/6);双生成器、include 闭包、dirty-surface、doc 引用绿。未完成:R-1 序列化(研究结论:留给 P3b/P4b 后的 P11 `gPipeInputs` 版本化)、线程放置(内核忽略亲和)、`SetHashSuppressor` 哈希等小项。报告:[`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md) | P5b | -| **P5e** 退役 Espryt draw path 的 lockstep | ✅ **已收官(2026-09-19)**,附一条具名未决:E1 对照需按 ID-114 重新定义 | 契约 `MG_Remote/CONTRACT-P5E.md`(规则 F:**未设障**记录的 apply 不得读任何 client 内存;ID-84 限定它只约束未设障记录);裁定 **ID-80..136**(`~/w7/notes/p5e/INTEGRATOR-DECISIONS-P5E.md`)。**十二个包,三波**:wave 1 **c0e**(契约 + 线上行:`PipeCalls.def` 第五列 `WaitClass`、`set_program_bindings` opcode 80、`kCapRunAheadApply` 位 10 仅 DirectGLES 臂、`kDrawClientArrays`、子系统位 13、两个旋钮)→ **id**(registry 按 `{slot, gen}` 重键、by-handle resolver、分配器守卫);wave 2 **vi / sb / pg / tx2 / fb** 五个 per-draw 家族 ∥ **ra**(等待规则、present credit、`gPipeInputs` 变 server 角色内存),加 **fix1**(设备发现的 monolith 臂回归,ID-107);wave 3 **pa**(program 家族离开前端)、**mv**(multi-draw + 五个档位的门禁条目)、**gl**(门禁机制本身 + 两处潜伏崩溃)、**ra2**(翻开关后暴露的竞态)。**`kMGPipeP5eRunAheadReady` 与 `kMGPipeP5eClientWaitRuleLanded` 均已翻**。最大的那次迁移(逐 draw 的 `GetProgramForDraw`)**没有改动任何线上结构**:记录早就带着逐 link 的 `ProgramArchive`,缺的是仍去问前端对象的读者。报告 [`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md) | 出口门已逐条跑过(BRIEF-P5E §3,2026-09-19 在集成树上,run-ahead 已武装):**item 1** unit 在 strict 两臂均 **2256/2256**;**item 2/3** `integration-split` **179/179**、逐条普查零 `Fatal{`、`Admitted{` 恰为八行;**item 4(a)** `RUN_AHEAD=0` 作为配对对照 **179/179 绿**;**item 4(b)** `VERB_BARRIER=0` **变红**(车道 62/179);**item 4(c)** Magma 逐字记下 `run-ahead requested, server does not publish kCapRunAheadApply - running lockstep`,而 Espryt 对照记下 `run-ahead ARMED`;**item 5** 在集成树上重跑 red-once(ID-121 重排后的形式):倒掉 pa 的臂得 `GetProgramForDraw@DrawArrays`、倒掉 mv 的臂得 `GetBoundVertexArray@DrawArrays`,**恰好各一个具名对**,还原后各自转绿;**item 7** `MOBILEGL_IPC_AUDIT=1` **179/179**。**唯一未过的是 E1 对照脚本本身**(ID-122):已修掉它两个遮蔽性缺陷(选中了一个在本车道永远跑不起来的阳性对照用例;ctest 与日志助手对“谁被选中”意见不一致),但它索要的 `Fatal{BarrierViolation}` 在整次运行里 **出现 0 次**:`BATCH_WAITS` 默认值 1 下那个重叠不发生,而 run-ahead 武装后 `VERB_BARRIER=0` 还会顺手关掉 run-ahead(ID-114),它产生的那条臂**既不是 lockstep 也不是 run-ahead**。E1 需要重新定义它要证伪什么,不是再调阈值;在想清楚之前不动它的计数规则,否则只是换一种伪绿。G1 仍由 CI 断言(ID-123) | P5d | -| **P5f** 一切状态上 wire | ✅ **已收官(2026-09-20)**;验收代码 Windows `c42577a4` / WSL `cfca93c7885f` | f0 普查、f1 双块、fc 控制帧、fe Espryt 字段、fm Magma record consumers、fs 静态世代/角色及 liveness、fr registry、fv 反向回调均已合并。字段归属 **41 RECORD_SUPPLIED / 6 APPLIER_DERIVED / 0 BARRIER_PULLED / 16 FATAL**;strict / dualblock 预期 marker 集合均空。控制帧与静态量工作已完成,不再列为 P6 待实现项 | unit **2310 = 2300 PASS + 10 skip**;GPU **1377** 零失败;ordinary / strict split **180 = 176 PASS + 4 skip**;双块 **212 = 206 PASS + 6 精确具名 skip**;双后端 RSP 门 **2 PASS / 0 skip**、逐帧 rsp=0。G1 `.text` 10806051、symbols 27815 恒等且 0/0/0/0;G2 3016 同名;G14 3646→3744 无删名。逐包与收官修复均有 red-once;Claude 异族终审 P1/P2 已修;Redmi 六 clean-boot 臂 **60 PASS / 6 skip / 0 failed**、36 个窗口 rsp=0。详见 [`close-report`](notes/p5f/close-report.md) | -| **P6** spawn transport | ✅ **已收官(2026-09-22)**;全部包已落地于 `feat/disaggregated`(头 `77cbd176`,2026-09-21),出口门第 1–6 项此前已达成,第 7/8 项于 2026-09-22 采齐。收官前核出的三项:server 进程侧 `PipeStats` 从未初始化(`srv=0 srvpark=0` 是结构性零,门 8 无 server 半边)**已在收尾中修好**;184 符号棘轮 CI 门(契约 §12.1)与契约 §2 分给 hs 的 `LinkTerms` / `Refuse` / 指纹拆分**未落地**,移交 P6.5(负控 S6 随之) | 十包全落地:**a6** 进程装配/链接边界审计 → **c6** 规范契约 + 数据面缝惰声明 → **lk** `RingControl` 按写者重组 → **so** `SocketTransport`(AF_UNIX SOCK_STREAM + SCM_RIGHTS,12 条契约用例)→ **sm** 两个独立启动的进程(非 fork,name 汇合、共享匿名段,四守卫接线)→ **cp** EGL 控制帧跨进程并应答(`InitCapabilities` wire kind 11)→ **hs** build stamp 显式 present 入指纹 + `abiMajor/Minor` 补检 + 真实 pid → **dl** device-lost 闩(取自描述符挂断绝不取自超时)+ `Session::Fail` 漏斗(`FatalFamilies.def` 词表 + 90 站点归一 + `SessionFault` 帧命名家族)+ Fatal 普查门 → **st** fb-slot memo 具名 Fatal → **t6** `integration-spawn` 车道(102/102)+ 名集合结构性一致门 + per-role 日志(`.client.log` / `.server.log`)。Android CI 新增 `spawn-acceptance` 臂,App 从 `nativeLibraryDir` 解析 server 路径。真机 Redmi `2f7cbe2e` 上 `OpenRA.DirectGLES` spawn ssim=1.0 连跑三次 + 6 case 绿;`DirectVulkan` 真机分离路径不确定分歧(inproc 与 spawn 同表现→ Magma × 真实 GPU,非 P6,桌面 lavapipe 为 1.0)。边界仍为 pbuffer / surfaceless / 离屏;真窗口归 P12。契约 [`MG_Remote/CONTRACT-P6.md`](../../MobileGL/MG_Remote/CONTRACT-P6.md);a6 产出 [`a6-audit-v1.md`](notes/p6/a6-audit-v1.md)、[`a6-link-experiment.md`](notes/p6/a6-link-experiment.md)(**server 不能不链 `MG_Impl`**:184 符号,P7 101 / P3b-P4b 14 / 共有 60 / P6 自己 6);终局重审 [`P6-ENDSTATE-REVIEW.md`](P6-ENDSTATE-REVIEW.md)。**收尾四项(2026-09-22,工作树未提交)**:wire 计数器——`PipeStats` 新增 `ByteClass::StageSegmentBytes`(`PipeWireCodec.cpp:878` `StageAllocate` 唯一收口)与 `CallClass::WireRecords`(`:1188` `EncodeRecord` 提交路径)+ staged-blob 直方图,汇总行新增 `seg=` / `wrec=`;`ServerMain.cpp` 补 `PipeStats::Init()` / `Shutdown()`(spawn server 此前从不武装计数器——修前 0 条汇总行、修后 29 条,SSIM 1.0 不变);`MOBILEGL_PIPE_STATS_FILE` 按角色派生 `.client.json` / `.server.json`(走 `RoleLogPath` 同一导出规则,基名不指向文件);工具入库 `tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}` 与 `tools/trace_replay/run_android_retrace_local.py`(+19 行 `MOBILEGL_TRACE_PACKAGE`),`pin_device.sh` 新增 `2f7cbe2e` 条目(本板 GPU 钳 1050 MHz:`thermal_pwrlevel` 写 0 读回 1、`max_gpuclk` 只读 1 050 000 000;绝对值与 1100 MHz 时代的 `35d0befa` 行不可比)。 | 出口门(契约 §9)1–6 ✅:G1 pull 0/0/0/0、`.text` 恒 `0xa52203`;`integration-spawn` 与 split 名集合一致(可比 102==102)且同数绿;进程树运行时恰多一子进程、结束为零;每条 spawn 条目记子进程 pid 与 `transport=spawn`(`--require-spawn` 门);每包 red-once。**7/8 已采(2026-09-22)**:**门 7** 真机 reboot-clean 同热窗口配对 A/B(Redmi `2f7cbe2e` / M332BF / SM8750 / Adreno 830v2,CPU 定频 little 1555200 / big 1958400,GPU 钳 1050 MHz(本板 `thermal_pwrlevel` 写 0 读回 1、`max_gpuclk` 只读 1 050 000 000;绝对值与 1100 MHz 时代的 `35d0befa` 行不可比,只有同会话配对可比),`minecraft-1.21.4-rd12-odinlite-in-world` + DirectGLES,~1471 draws/帧;六臂交错 × best-of-3:18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal`;client CPU p50 ms/帧 inproc 7.880/7.889 vs spawn 7.876/7.869,均值 **−0.15%**,符号随臂序翻转,臂内散布 0.2–4.2%;第二会话 inproc/spawn 交错 × best-of-2:4/4 OK、8/8 `PINNED`、0 `Fatal`,7.987/7.989 vs 7.992/8.009 = **+0.16%**)——判读 **tie**(差值在臂内噪声内),如实记录、不设门;monolith 基线约贵 30%(−29.8% / −29.9%)。**门 8①** socket 门铃 vs inproc condvar:client 121.1 waits/帧、5.6–6.1 parks/帧,两传输逐字相同(run total 30 401–30 407);server inproc 10 866–11 963 waits/帧、5.0–6.2 parks/帧,spawn 10 415–10 416 waits/帧、4.66–4.90 parks/帧——socket 阻塞读**既不更便宜也不更贵**(契约 §9「阻塞读可能更便宜」未兑现),spawn server waits 少 8.9–12.9% 归因于 handoff 纪律而非门铃本身,唯一 socket 略差的列是 `clipark/f`(6.07 vs 5.60/5.75)。**门 8②③**(inproc,真实 trace retrace):`SEG_STAGE` 字节/帧 OpenRA 130.8 KB median / 8.01 MB steady mean(含启动负载 8.07 MB),rd12 816.2 KB median / 1.53 MB steady mean(含负载 10.79 MB,rd12 首帧 981 654 draws vs 稳态 ~1471,run-wide mean 约 7× 稳态,两个读法都报);分块后记录/帧 OpenRA 211 median / 206 mean,rd12 7 497 median / 7 504 mean;`maxrec=1808` 与分块前相同(默认 8 MiB chunk 预算在这些负载上从未被打到)。证据 [`notes/p6/gate7-device-ab.md`](notes/p6/gate7-device-ab.md) / [`notes/p6/gate8-doorbell-device.md`](notes/p6/gate8-doorbell-device.md) / [`notes/p6/gate8-wire-counters.md`](notes/p6/gate8-wire-counters.md) | -| **P6.5** 传输栈两轴化 + 跨机 | **第一波已落地**(`feat/disaggregated@fe28bdb5`,2026-09-22):全 TCP 控制 + StreamLink 数据,WSL client ↔ Redmi server。四切片代码审查无硬缺陷 + 两修复(`AcceptPair` 重试 `EINTR`/`ECONNABORTED`、supervisor 自设 `MOBILEGL_IPC_ROLE=server`,均 `MG_Remote` 门内 G1 不动,见 [`code-review-findings.md`](notes/p65/code-review-findings.md));固定制品(两端 stamp `p65-acceptfix-20260922`)再验收:unit 2399、integration-tcp loopback 102、**device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED。**残余(CI/后续)**:完整 39 例 device golden 矩阵 + 必测数。设计见本表下方「立即的下一项」节 | 包:**wf** wire 定宽与布局摘要——`wireFingerprint` = `PipeFields.def` 派生的逐成员 name / `offsetof` / `sizeof` 摘要 + 有序目录摘要 + 记录头布局 + 两个 blob codec 版本 + `kOpCount` + 字节序 / 指针宽度,**保留** `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`(wire 事实)、删 `sizeof(GLFunctionsTable)`;`buildFingerprint` 只在 `Dial == Fork` 下比较;`DynamicBackendParameters` / `MGPCaps` / `RenderStateParameters` 整块 blob 的定宽化(两端是不同编译器产的不同二进制,`long` / padding / 位域差异都是静默读错状态);`Refuse{code, detail, 对端值}` 控制消息,握手路径零 abort → **ct** 控制面端点种类(`fork` 继承 fd / `unix:` / `tcp://host:port`;`DialMode{No, Fork, Connect}` 与 `MGPipeShouldDial()` 按契约 §3 补齐;TCP keepalive 失败**作为传输层报告的挂断**进 dl 的闩,不是 apply 超时)→ **nd** 数据面协商(`LinkTerms{dataPlane, delivery, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 入 Hello / Welcome,四个尺寸由 **server** 陈述;`dataPlane ∈ {SharedSegments, Stream}`;`auto` 的判据是**段实际交付成功**而不是地址判断,回落必须具名入日志与 stats;链路工厂在会话建立时选定一对,之后不变——缝是 setup-time 的,契约 §8.2)→ **sd** 共享段交付与控制面解耦(`ShmLink` 自带 AF_UNIX aux 汇合名、在 `Welcome` 里公布,`SCM_RIGHTS` 只走它;于是**控制面 TCP + 数据面共享段**在同机是合法配对;`ITransport` 收窄为纯控制帧,描述符传递归数据面)→ **sl** `StreamLink`(分块封帧;watermark 变消息且**规则逐字保留**——late-never-early、`>=` 不是 `==`、倒退是 Fatal;`appliedSeq` 修正案 + flush-on-idle;reply 变自带 seq 的消息,本链路上删 slot 池 / 取模 / stamp 自检;`SEG_STAGE` 变发送窗口,三个 cursor 要么完整复活要么不用、不留中间态;program archive 与 `draw_vbo` range 尾分片;`GetCaps` 两个 blobref 变消息;每方向每进程一条专用读线程)→ **t65** 车道。上层纯度:`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder`、`ClientSession.cpp` 的 `kSegEvent` 挂载与 `ResourceTracker.h` 的 writeback 解析都只经 `ILink`。设计 `ARCHITECTURE.md` §11.9 | 门:inproc / spawn 车道结果数与逐线程 CPU 逐字不变(`ILink::ReserveRecord` 虚调用代价**实测**,`RingProducer::Reserve` 失去内联是具体风险);纯度 grep——`ShmLink` / `StreamLink` / `RingControl` / `ReplySlotPool` / `EventRingConsumer` / `m_shm.` 只出现在 `Transport/` 与其测试,上层零链路种类分支;**2×2 矩阵车道**(控制面 {unix, tcp} × 数据面 {shm, stream},loopback)名集合与 spawn 一致且同数绿,每条目记 `control=… data=…` 的 arm 证明(ID-124 的形状);跨机臂一条真 trace 逐像素;wf 三条 red-once(临时树里同宽成员互换 → 摘要变而 stamp 不变;`wireMajor = 99` → 两端具名拒绝、server exit 0、client 不回落 monolith;仅 stamp 不同的对端在 `Dial != Fork` 下连接成功——正控制);**必测数**:逐帧 `kWaitReply` 次数(`ResourceCreate` / `SetTextureParams` / 纹理半边 `ResourceSubData` 都在此列,TCP 上每次即一个 RTT)、`SEG_STAGE` 字节/帧对链路带宽、`PRESENT_CREDIT` 1..3 配对、混搭(tcp + shm)对(unix + shm)的逐线程 CPU 差 | -| **Ph** 不可信对端加固 + 配对 | **已审计(2026-09-22,`notes/p7/PLAN-PH-P34B-P7.md` §1.1)**:P6.5 已在 LAN 上跑 `tcp://0.0.0.0`,「P12 之前」的顺序陈述落后于树。必需小件(supervisor 命名子进程死亡、两处漏斗旁路、D11 五处上限、常量时间令牌 + 数据面绑定、`SEG_EVENT` 弃投闩)随 P7 波次推进;fuzz 臂 2 在 P7 之后;OQ-21 已裁定令牌足够(ID-P7-3)。**进展(2026-09-22 深夜)**:supervisor 命名子进程死亡 + 两处头层漏斗旁路已随 wave 0 落地(`origin@801f8eda`,`abort_sites` 79 成下行棘轮);其余小件是簇 **F** 包:**片 1–3 已到集成树(2026-09-22 夜,ID-P7-37)**——令牌合一 / ≥16 字节具名拒绝 / 无令牌只 loopback / smoke 进 CI;`Welcome.dataNonce` 绑定,TCP 上 250 ms 配对窗口消失,`wireFingerprint` 变更;PH-8 钳制证明——片 4–6 与 PH-7 (5) 需要对端字节的 fuzz 驱动,另派 F2(原顺序:常量时间令牌 / `Refuse{Authentication}` / ≥16 字节 + `tcp_supervisor_smoke.py` 接 CI → `Welcome.dataNonce` 数据面绑定 → PH-8 单测 → D11 五处 + PH-2 → PH-6 drop-with-latch → PH-1 (3)(4));逐日状态见 [`CURRENT_STAGE_STATUS.md`](CURRENT_STAGE_STATUS.md)**F2 完成(2026-09-23,ID-P7-57)**:PH-1 (3)(4) 每会话闩、PH-6、PH-7 (5) fork 前认证 + 退避、fuzz 三臂进 CI——Ph 必需小件全部落地。**codex closeout(p7/int3)**:PH-5 界改为元素最小编码字节数(finding 3);`DrainRing` 在每次 pop 前查闩(finding 6:控制线程闩的窗口缩窄到查闩到 pop,未关闭)。 | `Session::Fail` 的策略翻成每会话闩(**按 ID-P7-1 重定界**:98 处 `SessionFail` + 6 处 `WireLogFatal` 的字面翻转拒做——≥9 处返回引用没有诚实返回值;只对对端字节可达站点 latch-and-decline;P6 dl 收口到的是**两个漏斗 + 两处头层旁路**(`Server/StagedShadow.h`、`Server/StagedTextureStore.h`),不是一处;P6.5 fork-per-session 已给出「下一连接照常服务」);handle slot 预算(`BackendSlotTable` 的两条拒绝从 `MOBILEGL_ASSERT` 变 release 下的具名拒绝,杜绝静默 null twin);readback 尺寸门挪 server 侧且在 resize **之前**设绝对上限;`StagedTextureStore` 按该 level 宣告范围设界;`ArchiveVector` 按 `count × 元素最小编码字节数` 设上限(codex closeout finding 3 改自 `count * sizeof(element)`,后者拒掉编码器自己写的紧凑归档;map / set 仍按每项 1 字节);`SEG_EVENT` 溢出不再能让一个不排空(或仅涓流排空)的对端在 30 秒内、乃至瞬间用 `Fatal{EventRingOverflow}` 打掉承载它的 server 进程(drop-with-latch,不等 PH-1,ID-P7-2;原文「60 秒」是两轮 30 s 的算术上界);**配对 / 认证**(`Hello` 带令牌与无令牌只 loopback **已由 P6.5 ct 落地**;欠常量时间比较、`Refuse{Authentication}` 入词表、≥16 字节、**数据面按 `Welcome.dataNonce` 绑定到已认证的控制连接**(今天 `AcceptPair` 按到达顺序配对、从不查 peer)、fork 前认证;OQ-21 裁定令牌足够、不做 TLS);段 / 窗口尺寸由 server 钳制(P6.5 nd 已陈述,欠一条钳制单测) | 非 loopback 监听在无令牌时具名拒绝(机制已在;名字入词表 + `tcp_supervisor_smoke.py` 接 CI 后算达成);畸形帧 / 超界计数 / 不排空对端的模糊臂下 server **不 abort、会话闩住、下一连接照常服务**(臂 1 机制已在、欠端到端脚本;臂 2 = PH-1 (3)(4) + D11;臂 3 = PH-6);`Session::Fail` 站点普查门扩到 server 镜像且 `abort_sites` 变棘轮(今天只扫 `MG_Remote`、站点数记了不比,wave 0);契约 §12.2 五处无界分配各一条 red-once(**五处全开**,不是两处) | -| **P3b / P4b** 深化(Espryt) | **已审计(2026-09-22,`notes/p7/PLAN-PH-P34B-P7.md` §1.2)**;Espryt 项与 P7 文件零重叠,作为 P7 的并行流(wave 2-D)推进。**D1 / D2 / D3 已落地(`origin@22761a69`,2026-09-22)**:D1 = XFB 两洞(孤儿目标保留 varyings、回写事件切片)+ 回调 10→9 + 四个 resolver 具名启动期 stop + readback 家族三臂(+39/臂,readback 半边其实早已关,ID-P7-24);D2 + 返工 = `TextureViewAliasScenario`、`TextureUploadShape` 成门(散点内缩后零补丁 red-once)、`espryt_memo_purity.py`、`SubsystemDeps.def`(R-5)、12 场景 split 普查(+76/臂)(ID-P7-26/29);D3 = view 在 owner 上传后陈旧的产品缺陷修复(clean gate 读 storage record,三臂 3 红→14/14)+ D-K2 六处读者读同一张表(ID-P7-28/30)。**余**:R-3 另一半(`MGPSamplerView::Target` 谓词)、`KHR-GL46.texture_view.coherency` 的 split 读数、CTS AFTER(wave 4);~~Iris trace 普查结果(wave 3)~~ 已有头上结果(`3c80cd62`,[`notes/p7/iris-census-1135c664.md`](notes/p7/iris-census-1135c664.md));逐日状态见 [`CURRENT_STAGE_STATUS.md`](CURRENT_STAGE_STATUS.md) | view/属主发射游标**别名场景**(代码半已由 P4a `TextureEmit.h` 属主键控游标 + `TextureObjectView::ToOwner*` + P5e tx2 落地,欠验证);`g_fboTextureSyncList`(分离安全——记录臂先返回、退路是具名 Fatal;四个 resolver 改启动期具名 stop,文本删除受 P5e 裁定 1 约束归 P13);memo 重键**已落地**(P4a/P5f),补纯度 grep 门;~~XFB scatter 搬到 client~~(P5c/P5f 以 server staged shadow + `OnBufferWriteback` 落地;剩两处真洞:孤儿捕获目标被静默丢弃、回写事件无界——>128 KiB 即 `Fatal{EventRingOverflow}`);删 fragColor 重推导 workaround 与 `g_broadcastMemo*`(动 pull `.text`,归 P13,只改注释);raw-depth-fetch sampler 的 monolith 清理(可选);**回读收口**(pack 半已关:`set_pixel_pack_state` APPLIER_DERIVED + server 中性读 + client 侧 PACK-PBO 散射;readback 半开:`get-tex-image-shadow` / `copy-image-shadow-mirror` 非 monolith 下仍 abort、split 零覆盖);P4a 的 R-5(D-K2 规则合一处)/ R-11 必须,R-3 / R-4 / R-7 / R-10 可选;~~`ProgramArtifacts.h` 的 NDK 尺寸钉~~(P6.5 portable archive v2 + 布局指纹取代);链接实验分给它的 **14 个符号**(D12,棘轮落地后 Tier 1) | 纹理 / program 场景(split 三臂零覆盖,先普查首阻塞再注册);CTS `texture_*` / `shader_image_*` / `packed_pixels` + P4a 欠的 `direct_state_access.framebuffers*` 在 0.5 pp 内(caselist 入库 + monolith 基线 + delta 工具,今天三者皆无);每一个 Iris trace(CI 管道 P6 已建;**`3c80cd62` 主机结果**:39 例 × 双后端 × {monolith, inproc, spawn} 231 次全活,split 与 monolith 逐字节相同 70 / 77 行,7 行分歧均过阈值、无 Fatal;`texture-remint-pull` 未到达,**P9 例外表为空**——[`notes/p7/iris-census-1135c664.md`](notes/p7/iris-census-1135c664.md) §3–§4);`TextureUploadShapeScenario` 升级成门(Mali 增量无设备,按 Adreno 记录) | -| **P7** DirectVulkan(Magma)全量迁移 | **✅ 已收官(2026-09-23,ID-P7-63)**:§8 九项全绿,终局窗口 3 门 3 36/36、门 5 PASS。**此前:wave 0–2 已落地,40% 检查点 6/9 已过(2026-09-22,`origin@b089e0de` + 集成树上的 B3)**:审计 + 五波计划见 `notes/p7/PLAN-PH-P34B-P7.md` §1.3 / §3,规范 `MG_Remote/CONTRACT-P7.md`,裁定 `notes/p7/INTEGRATOR-DECISIONS-P7.md`(ID-P7-1..33)。**wave 0** 仪器(Magma 两进程车道三臂、`link_ratchet.py` 186 基线、普查门 79 站点下行棘轮 + `MGPipeSessionFailHook`、设备跑器 `--matrix/--repeat/--archive-dir`、CTS caselists)`origin@801f8eda`;**wave 1** 设备窗口 #1(门 3 分母 36、E0–E7 归因、CTS `$BASE` 五块);**wave 2** X1(arm64 persistent-map tracker 从不认领 fault)、F1(split 起会提前)、C(`StateObjectDeathOps`、OQ-8、OQ-10、vertex-layout 拆分)、A(对齐 / native-range)、B(mip 半退役、深度 mip 烘焙 (A)(D)、copy-image、default-color-blit、回读 fence)、B2(multisample shape/aspect、AllocatorDebtScope ×2 删、(B′)、棘轮 **186→173**)、B3(**OpenRA 真机分歧的真因**:wire 臂 completed-frame-serial floor 在中帧 pooled-fence 提交退休时无证据上升 → 帧首次 `glBufferSubData` 走无序 host memcpy;计数 26→0,spawn 臂 red-once;ID-P7-33)——**树上 `@P7` 拒绝站点归零、§3.2 六退役全成、Magma 三臂 95/74/76**。**真机**:p7w4 门 3 AFTER 34/36 与 monolith 差 ≤ 0.0005;OpenRA p7w5 **27/27 golden**(ID-P7-34,项关闭);bsl-esc-menu = server 死 VkBuffer 无界累积(一帧 1.3 M 调用,ID-P7-32,**M2** 在修)。**已到集成树(2026-09-22 夜,ID-P7-37)**:B2 返工 r1 + r2、B3 返工 + 修复轮、V1、F 片 1–3 已推送 `origin@87584d0b`(2026-09-22 夜,ID-P7-39;pipe 门全绿:magma 97 / 76 / 71、full 535、verify 构建 1140 / 1074、OpenRA split 回放 0 条 unsound;四份 fable 审查 land with fixes,修复轮已落地,ID-P7-44);**M2 + M2 r2 已推送**(主机 bsl spawn server 825 → 546 MiB;memo destroy-epoch 关闭 wire store 句柄复用的 ABA;第二个泄漏 = descriptor set → M3;ID-P7-46);V1 修复 r2 已推送(read 侧负控数到 2、FATAL=0 摘要在 Close 前写出,ID-P7-47);**在跑**:F2 待派;窗口 1b 矩阵在跑(配对窗口在直连 LAN 上同样触发,ID-P7-41)。提交尾注已按用户令改写(origin `3c80cd62`)。**2026-09-23 更新(ID-P7-49–54)**:M3 已在 7ea7ce89 推送;B4 已集成于 pipe b19297d5,源码审查 land、完整集成门全绿(ID-P7-51)。p7w6 DirectGLES × TCP 38 例对照与 Photon / D24 同 APK monolith 对照已结案(ID-P7-52/53);两项错图亦在 monolith 复现。F2 PH-4/5/PH-2 已在包分支 commits `2c6a6e25` / `51480f8c` 落地并通过 Luna 审查(ID-P7-54);该矩阵不替代门 3。**CI 与门 4(ID-P7-55/56)**:CI 自 P6.5 起的七个红因已修;棘轮 171,`p7-magma` 余 86 标 `# P13`,门 4 关闭。**F2 与门 5 修复(ID-P7-57/58)**:Ph 余项与 fuzz 三臂落地;门 5 预演的三个 inproc CTS 缺陷修掉;窗口 2 工装与判读裁定(ID-P7-59)。**窗口 2 与收官审查(ID-P7-60–62)**:门 5 PASS;门 3 严格 33/36,三例 Iris monolith 同样不稳定 → 分布判据后 36/36;codex 收官审查修复、spawn 冷启动心跳(协议修订 2)、1:1 blit 最近邻落地;终局窗口 3 复跑。**余**:F2 PH-6 / PH-1 (3)(4) / PH-7 (5)、fuzz 臂 2 与完整包门;wave 3 棘轮 88;wave 4 CTS AFTER 和门 3;wave 5 fuzz 三臂与整体审查。逐日状态见 [`CURRENT_STAGE_STATUS.md`](CURRENT_STAGE_STATUS.md) | `38919d45` 已补实际游戏的 server buffer consumers 和 Android 旋转 blit([报告](notes/p5f/magma-inproc-fix.md));`194382c9` 又完成 Magma run-ahead、GPU 延迟退休与 VAO 身份碰撞修复([报告](notes/p5f/magma-runahead.md));`ed507b1a..d56c0e83` 29 个 DirectVulkan 提交的 shape 波(随 `e1bf3677` 合入,本行此前未记)已退役 packed-float mip 原生 blit(`5310ff62`)、resolve(`ec4ca06d`)、占位图像(`WirePlaceholderImages.inc`)、copy-image 端点、XFB 捕获 span、纹理回读;`70fb6689` 链关闭 inproc RD32 性能回归(86.85→116.73 fps)。其余工作(2026-09-22 审计改判):~~`SetupDrawSnapshot` 探测字段塌成 dirty mask~~ 与 ~~`VertexInputStateFactory` 内容寻址 CSO~~ **本阶段拒做**(`SetupDraw` 在非 monolith 传输下分支到 `SetupWireDraw`,monolith 快照机制在 split 下不跑;无性能门;ID-P7-5);占位纹理原生化**已落地**(缺 split 车道条目);具名 UBO host payload(D-B8)走了直绑常驻 `VkBuffer` range 的备选,欠一次测量 + 裁定关 OQ-6;内部 shader 烘焙**半已落地**(颜色 blit `WireColorBlitSpirv.h`,仅 split 臂),欠深度 mip 烘焙 + `MOBILEGL_BAKED_INTERNAL_SHADERS` 新鲜度门(0 处);D18 按 ID-P7-6 读法一句闭。**新增必须项**:Magma `StateObjectDeathOps`(六处特判)、OQ-8 归档 `storageBlocks`(`MagmaProgramSource::EnsureStorageBlocks` 每 draw 重跑 SPIRV-Reflect)、OQ-10 `kCapResidentSubData` 从未发布(split 下双后端退回就地 memcpy)、两处 `MagmaP7AllocatorDebtScope`。**P6 真机发现(2026-09-22 归此)**:`DirectVulkan` 分离路径在 Redmi 上两次观测 0.988998 / 0.976494(inproc 与 spawn 同表现,pbuffer monolith 1.0;case 归属、重复次数未记录,原始证据不在树内;取自 run-ahead ARMED 下,**lockstep 对照从未在 golden 口径下跑过**——wave 1 的 E1)——Magma × 真实 GPU;树上 `@P7` 具名拒绝**实为 15 处 / 4 文件**(`multisample-blit-shape` / `-aspect`、`depth-stencil-mipmap`(已窄化到 D\|S 合并)、`vertex-layout`、`vertex-format-conversion`、`texel-buffer-native-format`、`unaligned-{texel,storage,atomic}-buffer-range`、`storage-buffer-native-range`、`uniform-block-native-range`、`uniform-buffer-byte-tail` / `-dynamic-offset`、`copy-image-in-place`、`default-color-blit-shape`;外加 Magma 无 `StateObjectDeathOps` 与 `buffer-legacy-arm`),**全部是绕过 `Session::Fail` 与普查门的裸 abort**;必须退役 `.6/.8/.9/.11/.12/.13`,其余改 decline 即可;链接实验分给 P7 的 **101 个符号**(D12)。`DynamicBackendParameters` 定宽重写**不再是 P7 的**——它是跨平台 wire 的前提,归 P6.5 wf | 集成 + trace 在 Magma 的 push 与 split 下全绿(inproc 半已是硬门;**spawn / TCP 臂今天 DirectVulkan 条目为 0**——四条 split 臂硬编码 `MOBILEGL_BACKEND_TYPE=DirectGLES`,wave 0 补后端维度);verify 零分歧(verify 今天只注册 monolith,verify+split 撞旧 `PipeRespecifyScope` 断言,先清);真机 DirectVulkan split ssim 回到 1.0(分母排除 rd12 in-world 与 create-indirect,ID-P7-4;必须带 `RUN_AHEAD=0` 对照臂);184 符号棘轮(契约 §12.1)下降 P7 的 101 个——原 "`nm -D libMobileGLServer.so \| grep glslang` 为空" 不可达,D12 裁定 server 镜像就是整个 `libMobileGL.so`;仪器 `scripts/link_ratchet.py`(存符号列表、只对新增红)归 wave 0;CTS 五块(`texture_*` / `shader_image_*` / `packed_pixels` / `direct_state_access.*` / `uniform_buffer_object*`)先立 monolith×DirectVulkan 基线再比 inproc,≤0.5 pp 且新 crash = 0 单独硬红。**再基线检查点:中点完成子系统 < 40% 立即重定基线**(分母 = `notes/p7/PLAN-PH-P34B-P7.md` §4 的 9 项) | -| **P8** emulation 下放 + 索引宿主镜像 + 协议广度 | 待排 | `MG_Impl/Pipe/HostResolve.cpp`(~~client 数组范围~~ 已由 `beba0256` 落地为 owned buffer;最大索引扫描、`*IndirectCount` 解析,逐站点 reconcile);`Server/IndexHostMirror` **待重裁**(树里 0 处;a6 §6 实测宿主索引 span 无 producer、client 索引已是 owned buffer——按 `kCapNeedsHostIndexBytes` 的真实消费者重新裁定,不照抄 `ARCHITECTURE.md` §10.3);multi-draw client indices(`MultiDrawElements+CLIENT_INDICES` 仍具名拒绝);CopyImage 镜像搬到 client;viewport-array 回放验证;`generate_mipmap` 计划 + CPU 回退纹素;`texture-remint-pull` 仿真下放(15 处具名拒绝);大 blob carrier 只剩 **program archive 与 `draw_vbo` range 尾**(a6 §6;与 P6.5 sl 共用同一分片);`kCapDriverOrderedXfbCapture`(0 处);~~无 present fence tick~~ 只记在 P10 | `'^DirectGLES\.Split\.'` 与 `'^DirectGLES\.'` 逐名相同;trace split 双后端 SSIM ≥ 0.99 含两个 `coherent_as_flush` fixture;`ClientArrayAfterComputeWriteScenario`;`create-indirect` 上 `roundtrips-per-frame` 读零;`index-mirror-bytes` 逐用例发布 | -| **P9** 反向通道 | 待排,**P6.5 之后**(2026-09-22 重定范围) | **异步 reply 语义**,机制建立在 P6.5 的消息式 reply 之上(不再造 `SEG_REPLY` slot 池——池今天就有,stream 链路会删它);2 MiB reply cap 与 chunked readback(契约 §10.2.3 从 P6 改判至此,ID-47);PACK-PBO 回读 fire-and-forget + client 侧 `MarkGpuWritten`(ID-57,P6 未做);十个回调里未武装的四个:`OnTextureWriteback`、`OnTexturePullRequest`、`OnMipLevelsGenerated`、`OnLog` 分级(`OnXfbScatterReady` 是零生产者、零消费者、无 EventKind 的死声明——随 P3b/P4b 删除,`kMGPipeCallbackCount` 10→9;`ClientSession.cpp:429`;`OnBufferWriteback` / `OnGpuWritten` / `OnSurfaceChanged` / `OnGlError` / `OnCapsInvalidated` 已武装);`OnGpuWritten` 收窄;`OnBufferWriteback` 批处理 + epoch 排序;`OnGlError` 有序(CONTRACT-P5C §4.2);纹理拉取四条缓解 + 终止符;XFB 命名空间(`DeleteTransformFeedback` 的 server 侧泄漏——按裁定无行、最后两个 class-C 之一)。~~`SEG_EVENT` 溢出策略~~ 机制已由 P5e 落地(`ARCHITECTURE.md` §11.7),剩余的"不排空对端杀宿主"归 Ph | 回读 / XFB 场景在 split 下绿;`TextureRemintPullScenario`(含无解用例);拉取计数逐 trace 发布;两条故障注入 | -| **P10** sync / query / present 节奏 | 待排 | ~~client 铸造 query handle~~(已是:`QueryCreate` 为 `kWaitNone`);轮询入口成门铃点 + `MOBILEGL_IPC_POLL_ESCALATE`(0 处;TCP 上是首要杠杆之一);DirectGLES 的 fence 完成度来自逐 fence 退休(`g_completedFrameSerial` 仍只在 `Present` 前进;Magma 那半已由 run-ahead 落地);非 present fence tick(唯一记载处);`present` 1:1(已是);~~credit 默认 1~~(已是默认,`ConfigLoader.cpp:414`);**`PRESENT_CREDIT > 1` 从未量过**(重审 §8.7)——带延迟链路上的首要杠杆,inproc 臂上即可取;roundtrip 计数器与输入延迟直方图(今天只有 `MapPersistentRoundtrips`);`SetSwapInterval`(最后一个手写 class-C,`EmitTables.cpp:1665`) | query / XFB / `AsyncCompile` 场景在 split 下绿;trace 上 draw / state / upload 路径 roundtrip 读零;零 timeout 轮询有界退出;credit 1..3 在 TCP loopback 与跨机臂上的配对记录;配对 A/B 记录 | -| **P11** persistent map 与 ≥16 MiB 采纳 | 待排,**同机臂专属、已让位**(2026-09-22) | T0 / T1 只对共享映射成立:`dataPlane = Stream` 上点名 T0 / T1 是具名拒绝并强制 T2(重审 §6 第 5 项),今天 `MOBILEGL_IPC_ADOPT_TIER` 0/1 已是 Fatal-at-use、`kSegAdopt` 只有枚举;前置开放问题 3。POST 探针档位选择(T0 主攻,Adreno 可选 T1,T2 回退);`SEG_ADOPT` 生命周期绑 `completedFrameSerial`;**`gPipeInputs` 版本化 / 双缓冲**(P5d 研究:draw barrier 延后一个 verb 的前提,须在 P3b/P4b 的句柄键控 twin 表之后)。**P5e 的更正**:P5e 退役 draw lockstep **没有**需要它——ra2 实测证明字段**值**已由 `SuppliedFieldMask` 分区,当时那次竞态在**元数据标量**上,修法是让守卫测事实而非断言未来(ID-135)。本行保留采纳存储的一般版本/时序问题;契约 §3.2 的 per-role stamp 已由 P5f 落地,不再是待办 | `LargeArenaAdoptionScenario` 在所选档下绿;26.3 与两个 Create fixture SSIM ≥ 0.99;Adreno 830 上 p99 帧时与峰值 RSS 对采纳基线(163→21 ms / 40→115 fps / ~400 MB)回归不超过 10% | -| **P12** Android 生产窗口路径 | **子集已实现、未收官(2026-09-24,自 `9f669e52`,15 提交 + 审查轮 8 提交)**:上屏 server 窗口、`WindowKind::ServerOwned`、无头 client、失窗 device-lost 已落地并过真机七项检查;审查轮 10 个问题已修(两个大项:批内失窗、会话间 Espryt 单元影子悬空;裁定 [`notes/p12/INTEGRATOR-DECISIONS-P12.md`](notes/p12/INTEGRATOR-DECISIONS-P12.md) ID-P12-5–11),主机门复跑,**真机未复测**(ID-P12-13);两个出口门均未打(见 [`notes/p12/PLAN-P12.md`](notes/p12/PLAN-P12.md) §3) | **按重审 §6 改小**:~~Service 收 Java `Surface`~~ 删掉 Surface 传递那一半——server app 拥有自己的 SurfaceView,client 根本没有窗口,于是 minSdk 26 没有扁平化 `ANativeWindow` 的 NDK API 这条约束不再成立;`WindowKind::ServerOwned`;两个后端各一条 `CreateEGLWindowSurface` 臂;`m_windowHandle` 四处写点 + `sameHandle` 去重键(a6 §6);DirectGLES `g_Display` / `g_Surface` / `g_Context` 去全局;`kEventSurfaceChanged` 的 Width / Height 缺口(Espryt 只发布格式,server 拥有显示时 client 默认 FBO 会停在 512×512 占位值);client 侧不再无条件发 `SetWindowHandle`(`BackendObject_Remote.cpp:215`——今天 FCL 里开 spawn,第一次 `eglCreateWindowSurface` 就在 server 进程 abort);server app 监听 `unix:` 与 `tcp://`(后者 Ph 之后);server 生命周期绑 Activity;cached-app freezer(SPAWN-PLAN §8.2);多 context(契约 §11);FCL env 与 plugin APK 开关表接线 | 两个门:(a) Minecraft 经 FCL **同机 spawn** 在 Adreno 830 双后端入世界,杀 server 产生干净 device-lost latch;(b) **另一台机器上的 client 经 TCP** 在 Redmi 上进世界(数据面 stream),并记录 P6.5 的必测数 | -| **P13** 退役 pull 路径 | 待排 | 删 `SnapshotFromGLContext()` 非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`;保留 `MOBILEGL_PIPE_VERIFY`;MGPipe recorder;删 `set_residual_value_state`;在计数器活着的情况下重调幸存缓存容量;建立模块 target 与边界(D12:`SOURCE_FILES` 是一张平表喂两个库 target,无模块可链)| `static_assert(sizeof(ResidualValueBlock) == 0)`;三道纯度门在非 verify 构建上转绿;recorder 金标建立;monolith 逐线程 CPU 不差于 P0 基线 | +| **P0** 卫生、度量、门、骨架 | ✅ 2026-09-05 | 边界计数器、调用目录 + 生成器 G1–G7 + CI `pipe-gates`、`MG_Remote` 骨架;spike A(应用进程 exec 第二个原生可执行文件)两台设备成立;spike B:只有 T0(AHB 交接)在两设备两后端完整读写 | [`notes/p0`](notes/p0/README.md) | +| **P0.5** 值头与制品头抽取 | ✅ `5d99ee43` | `MGPipeValueTypes.h`、`ProgramArtifacts.h`、include 闭包门、符号报告;`.text` 不变 | [`notes/p05`](notes/p05/README.md) | +| **P1** `PipeInputs` 与 verify harness | ✅ | 277 + 58 处读点换成 `MGB_CTX`;逐 verb 填充与 poison;G4 影子比对;verify 818 / 79 trace 零分歧 | [`notes/p1`](notes/p1/README.md) | +| **P2** 渲染状态 CSO + Track H 首片 | ✅ `738b289d` | Tracker、render-state CSO + 动态状态、`CsoCache`、残余块 1248 → 8、11 条 memo 删除;**GO/NO-GO:继续**(Release 边界代价 +6–12%,已接受) | [`notes/p2`](notes/p2/README.md) | +| **P3a** 句柄 wave 1:buffer、VAO | ✅ `fde5fda3` | `resource_*` / vertex-input 句柄化;五部分门全绿、G1 0/0/0/0;1 天 | [`notes/p3a`](notes/p3a/README.md) | +| **P4a** 句柄 wave 2:FBO / 纹理 / sampler / program | ✅ `8c458cd5` | 十四族接线、六种 kind 重键、`CompositeResolver`;全门绿;1 天;Redmi 上 P4a 自己 +3.4–5.7 pt(Espryt) | [`notes/p4a`](notes/p4a/README.md) | +| **P5** 传输 + inproc applier + 发射表 | ✅ `ff2994d9..37fc4fdb` | `InProcessTransport`、四种 flavour、lockstep verb barrier、persistent-map 块推送、G8;OpenRA inproc SSIM 1.0;收尾全门停在 E3(a),审查发现转 P5b | [`notes/p5`](notes/p5/README.md) | +| **P5b** inproc verb 迁移 | ✅ `37fc4fdb..82683d4a` | 43 槽 + sync / blit / mip 迁移,发射表 A=2 / B=54 / C=15;`integration-split` 107/107;**Redmi 首次以独立 apply 线程渲染四条目标 trace**,barrier tax +5.9–18.2% | [`notes/p5b`](notes/p5b/README.md) | +| **P5c** 共享内存读点归零 | ✅ `11ac3de6..b88e8487` | 审计出的 59 处直接访问归零:纹理 staged shadow、`SEG_EVENT` 反向通道、按句柄解析、控制记录、角色守卫;契约 `CONTRACT-P5C.md` | [`notes/p5c`](notes/p5c/README.md) | +| **P5d** inproc 性能专项 | ✅ `1f8de61b` | VD12 Minecraft 上 inproc 7-13 → 103-106 fps,client 线程 15.0 → 9.2 ms/帧 | [`notes/p5d`](notes/p5d/README.md) | +| **P5e** 退役 draw path 的 lockstep(run-ahead) | ✅ 2026-09-19(附具名未决:E1 对照需重新定义,开放问题 24) | 规则 F、`WaitClass` 列、present credit;十二包三波;strict 179/179;VD32 下 inproc 与 monolith 齐平,run-ahead 相对 lockstep p50 +69% | [`notes/p5e`](notes/p5e/README.md) | +| **P5f** 一切状态上 wire | ✅ 2026-09-20 | 字段归属 41 / 6 / **0 BARRIER_PULLED** / 16;双块演练;双后端逐帧 `rsp=0`;Redmi 六个 clean-boot 臂通过。后续:Magma run-ahead(`194382c9`)、CI / inproc 功能补齐(`e1bf3677`,2026-09-21) | [`notes/p5f`](notes/p5f/README.md) | +| **P6** spawn transport | ✅ 2026-09-22 | 十包(a6 … t6);`integration-spawn` 102/102;device-lost 闩与 `Session::Fail` 漏斗;门 7 真机配对 A/B **tie**(inproc vs spawn ±0.16%);门 8 三个强制性能数已采 | [`notes/p6`](notes/p6/README.md) | +| **P6.5** 传输栈两轴化 + 跨机 | 第一波已落地(`fe28bdb5`),未宣布收官 | 全 TCP 控制 + `StreamLink` 数据,WSL client ↔ Redmi server;`wireFingerprint` / `Refuse` / 令牌;device-102 102/102 全 run-ahead ARMED。**残余**:39 例 device golden 矩阵与链路必测数;第二波(同机 TCP + 共享段)未开工 | [`notes/p65`](notes/p65/README.md) | +| **Ph** 不可信对端加固 + 配对 | 必需小件已落地(随 P7 F / F2,ID-P7-57) | 每会话闩、常量时间令牌、`dataNonce` 数据面绑定、预认证上限与退避、`SEG_EVENT` 弃投闩、fuzz 三臂进 CI;OQ-21 裁定令牌足够、不做 TLS | [`notes/p7`](notes/p7/README.md)(Ph 行) | +| **P3b / P4b** Espryt 深化 | D1 / D2 / D3 已落地,余项并行 | XFB 两洞、回调 10 → 9、上传形状成门、view 陈旧缺陷修复。余:R-3 另一半、`texture_view.coherency` 的 split 读数、CTS AFTER | [`notes/p34b`](notes/p34b/README.md) | +| **P7** DirectVulkan(Magma)全量迁移 | ✅ 2026-09-23(ID-P7-63) | §8 九项全绿:Magma 两进程三臂、`@P7` 拒绝归零、`StateObjectDeathOps`、verify × split、门 3 真机 ssim 36/36、门 5 CTS 五块 ≤ 0.5 pp;链接棘轮 186 → 171 | [`notes/p7`](notes/p7/README.md) | +| **P12** Android 生产窗口路径 | **当前**:子集已实现、未收官 | server 自建窗口上屏、`WindowKind::ServerOwned` 无头 client、失窗 device-lost;真机七项检查已过(审查轮后待复测)。出口门 (a) FCL + 杀 server、(b) 跨机 TCP 入世界均未打 | [`notes/p12`](notes/p12/README.md) | +| **P8** emulation 下放 + 索引宿主镜像 + 协议广度 | 待排 | client 侧 `HostResolve`、multi-draw client indices、CopyImage 镜像、mip CPU 回退、`texture-remint-pull` 下放;索引镜像按真实消费者重裁 | [`notes/p8`](notes/p8/README.md) | +| **P9** 反向通道 | 待排(P6.5 之后) | 异步 reply、chunked readback、PACK-PBO fire-and-forget、武装剩余回调、XFB 命名空间 | [`notes/p9`](notes/p9/README.md) | +| **P10** sync / query / present 节奏 | 待排 | 轮询入口成门铃点、逐 fence 退休、`PRESENT_CREDIT > 1` 实测、roundtrip 计数器、`SetSwapInterval` | [`notes/p10`](notes/p10/README.md) | +| **P11** persistent map 与 ≥16 MiB 采纳 | 待排(同机臂专属) | T0 / T1 档、`SEG_ADOPT`、`gPipeInputs` 版本化 | [`notes/p11`](notes/p11/README.md) | +| **P13** 退役 pull 路径 | 待排 | 删 `MGB_CTX` / `MOBILEGL_PIPE_PUSH` / legacy memo 臂 / 残余块;recorder;模块边界 | [`notes/p13`](notes/p13/README.md) | CTS 周转单独计价(`gl44to46` 约 56,271 例):逐阶段只跑该阶段可能影响的具名块;完整 caselist 只在架构边界与合并 `dev` 之前跑,放 CI 不放关键路径。 -## 立即的下一项:P6.5 第一波 —— 全 TCP 传输(client 在 WSL,server 在 adb 设备) - -> 状态:**已实现并落地推送 `feat/disaggregated@fe28bdb5`(2026-09-22),代码审查完成(无硬缺陷 + 两修复),固定制品代表性再验收通过。** 主机 unit 2399、integration-tcp loopback 102、Redmi **device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED;G1 与真实断线门通过。完整 39 例 device golden 矩阵与余下链路必测数按「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**;逐项见 [`CURRENT_STAGE_PROGRESS.md`](CURRENT_STAGE_PROGRESS.md) 与 [`notes/p65/`](notes/p65/)。目标形态:x86_64 的 WSL 进程作为 client,Redmi `2f7cbe2e`(aarch64,Adreno)上的 server 进程作为 server,两者在同一局域网内经 **TCP** 连接;`integration` 全部用例与 CI retrace 的 split 子集在这条链路上跑通并对上 golden。本节是 P6.5 行的第一波,只做「控制面 TCP + 数据面 stream」这一对;同机的「TCP 控制 + 共享段数据」混搭(nd `auto` / sd)是第二波,本节不做。 - -### 1 设计时的树上基线(实现前快照) - -以下表格保留本波开工前的缺口定位;当前实现与契约更正见 `MG_Remote/CONTRACT-P65.md` 和阶段进度,不以历史行号描述今天的树。 - -| 已有 | 出处 | 对本波的意义 | -|---|---|---| -| 控制面 `SocketTransport`:`Listen` / `AcceptPair` / `ConnectTo`,**汇合点是一个名字**,两条连接按序接受(第一条控制、第二条 aux);`auxFd = -1` 已是「这条链不传 fd」的诚实答案 | `SocketTransport.h:70-91` | TCP 只是第三种名字(`tcp://host:port`);两条连接的规矩原样用:第一条控制,**第二条从 aux 变成数据流** | -| 帧:`[u32 'MGLF'][u32 len][payload]`,`FrameReader` 是真正的字节流重组器 | `Framing.h` | 数据连接复用同一重组器,只换 magic | -| server 主程序:endpoint 来自 `argv[1]` / `MOBILEGL_IPC_ENDPOINT`;`Listen` → `AcceptPair` → 建 backend → `Accept`(Welcome + 七个描述符)→ 控制泵,**对端 EOF 即退出**;`MOBILEGL_IPC_DIAL=no` 反递归 | `ServerMain.cpp`(门 8 的收尾修改已落入该文件,本表不引行号) | 单会话进程形状保留为 **child**;设备侧要加一个 **supervisor** 循环 | -| client:`StartOverSocket(transport)` 已把「socket 怎么来的」与握手分开;spawn 路径 = 造 `@mgl--` 名 → `LaunchServer` → `ConnectTo` | `ClientSession.cpp:693-724` | `Dial = Connect` 只是**跳过 `LaunchServer`**、把名字换成 `tcp://` | -| 握手后 client 收 **7 个 fd**(4 段 + 3 个门铃)再 `AdoptFromDescriptors` | `ClientSession.cpp:832-897` | stream 上这一段整体不存在:段是本地私有的,门铃是本地条件变量 | -| **数据面没有缝**:`ILink` 只有声明,树里用到它的只有 `LinkProgress`(`Ring.h:97`);`ShmLink` 不存在;两端 session 直接持有 `SessionSegments`(`SessionRings.h:130`);`ServerSession.h:93` 自己写着「`lk` 把门铃挪到 `ILink` 后面并删掉这些访问器」——**没做** | `Ring.h:108`(`RingControl` 重组已做)、`ILink.h`、`StreamLink.h` | 重审 §5.1 那「约一周」的 lk 主体仍然欠着,是本波**第一个包**,没有它 `StreamLink` 无处可插 | -| wire 寻址:`MGPBlobRef{Offset, Size, Seg}` 只允许 `kSegStage`,解析集中在 `ResolveOrFatal` | `PipeWireCodec.cpp:1421`(拒绝其它段:`:496`、`:1401`) | **两端各建一个同尺寸的本地 stage 窗口、偏移一致,codec 一行不改** | -| reply:`ReplySlotHeader{Seq, Status, Size}` 16 字节、`seq % slotCount`(8 槽) | `ReplySlot.h` | 头已经是消息形状;stream 上删掉取模与 stamp 自检 | -| event:四种 kind、定宽头 | `EventRing.h` | 已是 wire 形状,原样装帧 | -| 指纹:`CapsAbiFingerprint()` = 三个 `sizeof` + 协议版本 + git stamp | `protocol.fbs` `table Hello` 的注释 | x86_64 ↔ aarch64 两份二进制:**必须换成布局摘要**,否则要么永远拒绝(stamp 不同)要么静默读错 | -| 日志按角色分文件;server 角色由启动方设的 `MOBILEGL_IPC_ROLE=server` 决定;`LogLine` 已在 `CtrlMsg` 里、**零生产者** | `ServerSpawn.cpp:116`、`ClientSession.cpp:1948` | 34 个读 `.server.log` 的消费方在跨机时读不到 → 日志前送(§3.5) | -| 车道:`mgl_itest_register_split_arms` 一个宏两条臂 {Split, Spawn},环境差只有 `MOBILEGL_TRANSPORT=spawn` + `MOBILEGL_IPC_SERVER_PATH` | `MG_IntegrationTest/CMakeLists.txt:1985-2010` | 加第三条臂 `Tcp`;名集合门(`scripts/ci/spawn_lane_parity.py`)与 arm 证明(`scripts/ci/junit_tally.py:27`)各加一臂 | -| retrace:桌面经 `MOBILEGL_TRANSPORT` / `MOBILEGL_IPC_SERVER_PATH` 环境(`run_trace_case.cmake:47-52`);设备经 `--env K=V;…` intent extra,server 路径从 `nativeLibraryDir` 解析(`TraceReplayActivity.java:374-386`);**桌面与设备共用同一套 golden**(`trace_cases.json`),驱动差异由 SSIM 阈值吸收 | — | 跨机 retrace 的 golden 就是现有的,不另造 | -| 设备已能从 app 进程 exec `libMobileGLServer.so`(spike A、`spawn-acceptance` 臂) | `apk.yml:477-487` | supervisor 就是这个可执行文件加一个 `--serve` | -| manifest **没有 `INTERNET` 权限** | `android-plugin/app/src/main/AndroidManifest.xml` | server 要监听 TCP,trace flavour 必须加 | - -### 2 拓扑:谁监听、谁拨号、为什么 - -server 在手机上**监听**,client 从 WSL **拨出**。理由不是偏好:WSL2 默认 NAT,出站到局域网直通、入站要 portproxy;手机侧无防火墙。`Role × Dial` 两轴(契约 §3)因此在本波是 `Role=Client, Dial=Connect` / `Role=Server, Dial=No`。备用形态:`adb forward tcp:40613 tcp:40613` 把 server 绑在手机 loopback、client 连 WSL 内 adb 的 loopback——**要求 adb 跑在 WSL 里**(adb-over-Wi-Fi:`adb connect :5555`),因为 Windows 侧 adb 绑的 127.0.0.1 WSL2 够不着;路由器开了 AP 隔离时用它。 - -### 3 机制 - -**3.1 端点与握手(包 ct)**。`SocketTransport::Listen/ConnectTo` 识别 `tcp://host:port`(其余仍是 AF_UNIX 路径);两条 TCP 连接按序:控制、数据。socket 选项:`TCP_NODELAY`(批处理由我们的 publish 点决定,不交给 Nagle)、`SO_KEEPALIVE` + `TCP_KEEPIDLE=2s / KEEPINTVL=1s / KEEPCNT=3` + `TCP_USER_TIMEOUT=5000ms`——Wi-Fi 掉线不产生 FIN,这是唯一能把「半开」变成挂断的机制;它以 `POLLHUP`/错误进入 `Doorbell::Dead()`,走 dl 的闩,**是传输层报告的挂断,不是 apply 超时**(开放问题 23)。`Hello` 追加 `linkTerms{dataPlane=Stream, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 与 `token`;`Welcome` 回填 server 陈述的四个尺寸;新增 `Refuse{code, detail, 对端值}`(`CtrlMsg` 只追加)。`buildFingerprint`(git stamp)比较策略:`Dial=Fork` 不匹配 → Refuse(抓陈旧 `MOBILEGL_IPC_SERVER_PATH`,不变);`Dial=Connect` 不匹配 → **警告**,`MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 时 → Refuse(车道设它:APK 与 WSL 构建必须同一 commit)。令牌:`MOBILEGL_IPC_TOKEN` 两端一致;server 无令牌时**只允许绑 loopback**,错令牌 → Refuse 并记日志。 - -**3.2 wire 定宽与布局摘要(包 wf)**。`wireFingerprint` = `gen_pipe.py` 从 `PipeFields.def` 生成的逐成员 `{name, offsetof, sizeof}` 表的摘要 + 有序目录摘要(opcode / payload 名 / flags / WaitClass)+ 记录头布局 + `MGPipeRenderStateSpans` 的 offsetof 表 + 两个 blob codec 版本 + `kOpCount` + `MOBILEGL_ABI_VERSION` + 字节序 + 指针宽度 + `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`;**删** `sizeof(GLFunctionsTable)`。x86_64 ↔ aarch64 都是 LP64、小端、Itanium ABI,布局大概率一致,但**由摘要断言、不由这句话断言**;已知的真实差异是 **`char` 的符号性**(aarch64 默认无符号)——wf 普查 payload 与 blob 里裸 `char` 当数用的站点。`DynamicBackendParameters` 定宽重写(从 P7 挪来)与 `RenderStateParameters` 1168 字节整块的成员表在此包。三条 red-once 见 P6.5 行。 - -**3.3 缝的主体(包 lk2,零行为变化)**。`SessionSegments` + `RingProducer/Consumer` + `ReplySlotPool` + `EventRingProducer/Consumer` + 两个门铃搬到 `ShmLink : ILink` 后面;`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder` / `ClientSession.cpp` 的 `kSegEvent` 挂载 / `ResourceTracker.h` 的 writeback 解析都改收 `ILink*`;`ServerSession.h:93` 说要删的访问器删掉。门:inproc / spawn 车道结果数与逐线程 CPU **逐字不变**(`ILink::ReserveRecord` 的虚调用在 ~850–3550 次/帧上实测;若不在噪声里,答案是把 reserve/commit 做成 link 自有 arena 上的非虚函数);纯度 grep 门上线。 - -**3.4 `StreamLink`(包 sl)——被消息复制的镜像段**。设计原则一句话:**两端各自持有一份同尺寸的私有段,链路只负责把字节搬到对面同一偏移;codec、watermark 规则、apply 线程、编码器全部不变。** -- 段:握手协商后两端各 `malloc` 四块:cmd 环(`cmdWindowBytes`)、stage 窗口(`stageWindowBytes`)、event 环、reply 邮箱。尺寸相同 ⇒ 偏移相同 ⇒ `MGPBlobRef` 与 `ResolveOrFatal` 原样工作。 -- 数据连接帧:`[u32 'MGLD'][u32 len][u8 kind][u8×3][u64 a][u64 b][payload]`。C→S:`Cmd{ringOffset, bytes}`(记录字节逐字、写到对面环的同一偏移)、`Stage{offset, bytes}`、`ClientProgress{cmdHead, submittedSeq, eventTail}`。S→C:`Progress{appliedSeq, retiredSeq, completedFrameSerial, presentAckSerial, cmdAppliedTail, eventRingFull}`、`Reply{seq, status, bytes}`(头即 `ReplySlotHeader`)、`Event{ringOffset, bytes}`。 -- client 侧:编码器照旧写本地 cmd 环与 stage 窗口;`Flush()` 把 `[上次已送, cmdHead)` 作为一帧 `Cmd` 发出(环回绕用两段 iov 一次 `sendmsg`),**之前先发本次新 stage 的 `Stage` 帧**——顺序由编码器「先 stage 后 reserve」保证;`LinkCapabilities.PublishIsDelivery=false`,所以每个等待点先 `Flush`(这个位就是为此设的)。C→S 背压:`WaitForCmdSpace` 不变(读 `Progress` 里的 `cmdAppliedTail`),`send()` 阻塞是第二层,没有无界缓冲。 -- server 侧:`mgl-srv-io` 读线程把 `Cmd`/`Stage` 拷到镜像段同偏移,再推进本地 `cmdHead`/`submittedSeq` 并敲 apply 的本地门铃;**apply 线程一行不改**。 -- `Progress` S→C 的发出时机(对 `Ring.h` 「`appliedSeq` 不得懒发布」的**修正案**,规则改成「最多晚一次 flush,绝不早」):(a) apply 线程**即将 park 前**(flush-on-idle,解决「帧内最后一条 `kWaitReply` 记录两端互等」);(b) 每次 `PostReply` 之后;(c) present ack / `completedFrameSerial` 变化时;(d) 每 64 条已 apply 记录或 1 ms 取先到者。接收侧对每个 watermark 做 `AdvanceMonotonic`,**倒退是 `Fatal{ProtocolCorruption}`**。 -- reply:`kWaitReply` 下同一时刻最多一条未决,client 侧是「单槽 + 条件变量 + seq 核对」;本链路上 slot 池、取模、stamp 自检**删除**(`ShmLink` 保留)。 -- event:server 的 `EventRingProducer` 写本地环,io 线程把新增字节装 `Event` 帧;client 读线程写进 client 本地环同偏移,`EventRingConsumer` 不变;`Drained()` 发 `ClientProgress{eventTail}`;server producer 在 `head − 对端 tail > capacity` 时**阻塞**(P5e 的规则原样,`eventRingFull` 经 `Progress` 回传)。 -- 门铃:`ConsumerBell()` / `ProducerBell()` 是本地 `CondVarDoorbell`,由各自的读线程敲;读线程见 EOF / 错误置 `Dead()`。每进程数据连接**一条**专用读线程(写在调用线程上),控制连接的读法不变。 -- 帧上限:`Cmd`/`Stage` 单帧 ≤ `kMaxFramePayloadSize`(64 MiB);一条记录本来 ≤ 4 MiB。未分片的 program archive / `draw_vbo` range 尾在 stream 上和今天一样受 `stageWindowBytes` 约束,超出仍是 `Fatal{RingOverrun}`——**不是本波阻塞项**,记在开放问题 11。 -- 不做的:`kSegShadow` / `kSegAdopt`(P8 / P11 的段)在 stream 上具名拒绝;`GetCaps` 的 blobref 若仍走 stage 之外 → 具名拒绝,本波不发明第三条载体。 - -**3.5 日志前送(包 lf)**。server 进程的日志 sink 在 `MOBILEGL_IPC_ROLE=server` 且有控制传输且 `MOBILEGL_IPC_LOG_FORWARD=1`(`Dial=Connect` 默认开)时,每行**同时**写本地 `.server.log` 并作为一帧 `LogLine`(已在 `CtrlMsg` 里)经控制连接发出;client 收到后写入**自己的** `.server.log`。于是 34 个消费方(`PipeStatsWindow` 的计数器窗口、四个 arming 场景、拒绝普查)**一字不改**。顺序:读者在 `MarkLaneLog` / `ReadLaneLogSince` 之前调一个导出的 `MGPipeSyncPeerLog()`(一次控制往返:`LogFlush` → server 冲刷并 ack),远端不存在时它是 no-op;导出符号一个,`-fvisibility=hidden` 下显式 `default`。若 lf 滑期,退路是这 34 处在 tcp 臂上**按名 SKIP**(清单文件,像 `dualblock-expected-fatals.txt`),不许静默通过。stats 行一帧一条,前送代价可忽略。 - -**3.6 设备侧 supervisor 与启动(包 sv)**。`mobilegl_server_main tcp://0.0.0.0:40613 --serve [--max-sessions 1]`:listen → 循环 { `AcceptPair`(控制 + 数据)→ `fork()` → child 关掉 listen fd、用 `SocketTransport(streamFd, dataFd)` 走今天的单会话路径到 `_exit`;parent 关掉已接受的 fd、非阻塞 `waitpid`、继续 accept }。每会话一进程 ⇒ `eglTerminate` 的进程级拆机被隔离(D9 原样);同时只服务 1 个会话,第二个连接 → `Refuse{Busy}`。启动:trace flavour 新增前台 `MobileGLServerService`(`android:process=":mglsrv"`),`am start-foreground-service` 带 `--es listen`、`--es token`、`--es env "K=V;…"`(复用 `--env` 的 `K=V;` 语法),它从 `nativeLibraryDir` exec `libMobileGLServer.so`(spike A 的路径)、给 child 设 `MOBILEGL_IPC_ROLE=server`、持 partial wake lock;`am force-stop` 整包停掉。后端由 client 的 `Hello.backendType` 请求、supervisor 的 env 可钉死,不一致 → Refuse。EGL 在 child 里是 pbuffer / surfaceless(P6 边界;真窗口归 P12)。manifest 加 `INTERNET`。 - -**3.7 谓词与测试的拓扑感知**。client 进程:`Transport=Spawn`(拓扑:两进程)、`Dial=Connect`、不 fork;`MGPipeBackendIsLocal()=false`。`CountOwnChildren()` 一类断言改为按 Dial 分支:Connect 下断言**本地零子进程** + `Welcome::serverPid` 非零 + 日志 `control=tcp`。S2(kill server)在跨机臂上经 `MGITEST_PEER_KILL_CMD` 模板(`adb -s shell run-as top.mobilegl.plugin.trace kill -9 {pid}`)真杀——**不**允许用协作式「请你退出」代替,那测的不是挂断。 - -### 4 包与顺序 - -`lk2`(缝主体,零行为)→ `wf`(定宽 + 摘要 + Refuse)→ `ct`(tcp 端点 + keepalive + 令牌 + Connect 拨号)→ `sl`(`StreamLink`,先在 **WSL loopback** 上对着 spawn 车道打平)→ `lf`(日志前送)→ `sv`(supervisor + Service + adb 脚本)→ `t65`(tcp 臂、跨机臂、门、数)。每包 red-once(R-16)。第二波(不在本节):`nd` 的 `auto` 与 `sd` 的 aux 交付(同机 TCP 控制 + 共享段数据)、P11 的 stream 具名拒绝、Windows client。 - -### 5 出口门(本波) - -1. G1 pull 0/0/0/0、`.text` 不变(全部新代码在 `MOBILEGL_BUILD_DISAGGREGATED` 下)。 -2. lk2 后 inproc / spawn 车道结果数与逐线程 CPU 逐字不变(实测,不论证)。 -3. **`integration-tcp`(WSL loopback,server 以 `--serve tcp://127.0.0.1:40613` 本地起)**:名集合 == spawn(parity 脚本加臂)、同数绿;每条目 arm 证明 `control=tcp data=stream server= pid=N`(`junit_tally --require-tcp-ran`)。 -4. **`integration-tcp-device`(server 在 Redmi)**:同一名集合;首轮**记录**、不设阻塞门,每一条与 loopback 臂的差异必须具名(GPU 能力 / P7 的 Magma 分歧 / 时限);第二轮起转硬门。 -5. **retrace**:CI split 子集(`trace_cases.json` 里未 `"split": false` 的)从 WSL 对着手机跑,DirectGLES 的 SSIM ≥ 现有阈值、OpenRA 为 1.0(E2 的锚);DirectVulkan 记录不设门(真机分歧是 P7 的)。 -6. red-once:wf 三条(P6.5 行);ct——错令牌 → Refuse、`wireMajor` 不匹配 → 两端具名拒绝、`adb shell svc wifi disable` → 10 s 内闩住(而不是 120 s `BarrierTimeout`);sl——`Progress` 倒退 → `Fatal{ProtocolCorruption}`、越窗的 `Stage` 帧 → 拒绝、去掉 flush-on-idle 后「帧内最后一条 `kWaitReply`」用例必须挂住并被超时抓到;lf——关掉前送后四个 arming 场景必须**按名红**;sv——第二个并发连接 → `Refuse{Busy}`。 -7. **必测数(记录)**:逐帧 `kWaitReply` 次数与 RTT 分布(OpenRA、MC in-world 各一);`SEG_STAGE` 字节/帧对链路实测吞吐(车道自己量一次 64 MiB `Stage` 突发);`PRESENT_CREDIT` 1 / 2 / 3 的帧率;loopback tcp+stream 对 spawn(unix+shm)的逐线程 CPU 差。 - -### 6 两边怎么配(第一波操作手册) - -**手机(server)** -```bash -# 1. 同一 commit 构建 trace APK(分离构建)并安装 -./gradlew :app:assembleTraceDebug -Pmobilegl.buildDisaggregated=ON && adb install -r app/build/outputs/apk/trace/debug/*.apk -# 2. 手机 IP -adb shell ip -f inet addr show wlan0 -# 3. 起 server(前台 Service → supervisor 监听) -adb shell am start-foreground-service -n top.mobilegl.plugin.trace/top.mobilegl.plugin.MobileGLServerService \ - --es listen tcp://0.0.0.0:40613 --es token devtoken-0123456789abcdef \ - --es env "MOBILEGL_BACKEND_TYPE=DirectGLES;MOBILEGL_PIPE_STATS=1;MOBILEGL_PIPE_STATS_PERIOD=1;MOBILEGL_LOG_FILE_PATH=/data/data/top.mobilegl.plugin.trace/files/mgl.log" -adb logcat -s MobileGL | grep -m1 'listening on tcp://' -# 停:adb shell am force-stop top.mobilegl.plugin.trace -``` - -**WSL(client)**,在分离构建目录(今天是 `~/lk-split`): -```bash -export MOBILEGL_TRANSPORT=spawn # 拓扑:两进程;不 fork,因为下面给了远端 -export MOBILEGL_IPC_CONTROL=tcp://:40613 -export MOBILEGL_IPC_DATA=stream -export MOBILEGL_IPC_TOKEN=devtoken-0123456789abcdef # ≥16 字节(PH-7 (3)):更短的令牌让 server 以 72 退出而不是监听 -export MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 # APK 与本地构建必须同 commit -ctest -L integration-tcp-device -j 1 --output-on-failure # -j 1:设备同时只服务一个会话 -# retrace(同一环境): -(cd build-retrace/tools/trace_replay && ctest -V -R '^MobileGLTraceReplay\.OpenRA\.DirectGLES$') -``` -日志:client 侧 `.client.log` 与前送来的 `.server.log` 都在 WSL;设备本地副本用 `adb shell run-as top.mobilegl.plugin.trace cat files/mgl.server.log`。 - -**备用(AP 隔离 / 没有局域网直通)**:adb 跑在 WSL 里——Windows 上一次 `adb tcpip 5555`,WSL 里 `adb connect :5555 && adb forward tcp:40613 tcp:40613`;手机侧 `--es listen tcp://127.0.0.1:40613`(loopback,不需令牌),WSL 侧 `MOBILEGL_IPC_CONTROL=tcp://127.0.0.1:40613`。 - -### 7 风险与未决(本波) - -1. **`kWaitReply` × RTT**:`ResourceCreate` / `SetTextureParams` / 纹理 `ResourceSubData` 每次一个 RTT(Wi-Fi 2–5 ms);MC 加载区块的创建突发会可感知地慢。本波**只量**(门 7),减少这一列是 P9 / P10 的活。 -2. **带宽**:`SEG_STAGE` 字节/帧对 Wi-Fi;单次 128 MiB 上传 = 秒级停顿(开放问题 19、20)。 -3. **Wi-Fi AP 隔离**:用 §6 的 adb forward 备用。 -4. **DirectVulkan 真机分歧**(P7)会在门 5 出现:记录,不当本波的红。 -5. **`MOBILEGL_IPC_ROLE`**:supervisor 必须替 child 设它,否则 child 的日志会当 client 写(`Log.cpp` 从这个 env 解析角色)。 -6. **ctest 并行 vs 一设备一会话**:`-j 1` 或 supervisor `--max-sessions N`(每会话一进程,N 个 EGL 上下文同时存在,Adreno 上未验证)。 -7. **同 commit**:两端二进制不同是常态,指纹只保证 wire 布局;`REQUIRE_SAME_BUILD` 是车道纪律,不是协议要求。 - -## 历史:P5c 计划与审计(`inproc` 共享内存读点;审计头 `a79a0af6`,2026-09-17) - -**为什么插在 P6 之前**:P5 / P5b 的 `inproc` 只把 verb 记录、`SEG_STAGE` blob、reply 与 caps 快照做成了 wire。对 `a79a0af6` 的只读静态审计(`~/w7/notes/p5c/p5c-audit-v1.md`,59 行清单,逐条在该头上重新解析)证明两个角色之间仍有一批**不经任何载体的直接内存读写**,靠 verb barrier 与同一地址空间才正确。它们不是传输问题:`spawn` 换掉的只是 ring 的传递方式,而这些读写在另一个进程里根本没有对应内存。P5c 的目标是让 `inproc` 诚实——两线程之间除 `SEG_CMD` / `SEG_STAGE` / `SEG_REPLY` / `SEG_EVENT` / caps 快照 / 控制帧之外零直接访问——从而 P6 只是传输替换。**本节保留 P5c 启动时的计划与审计语境;P5c 已收官,后续对象类、控制帧、静态量与反向通道的收口结果以 P5f 报告为准。** - -### 审计清单(按家族;`file:line` 以 `a79a0af6` 为准,完整 59 行见报告) - -| # | 方向 | 直接访问的是什么 | 站点 | 今天为何能工作 | 现有守卫 | P5c 载体 | -|---|---|---|---|---|---|---| -| T1 | S→C 读 | **纹理纹素**:`resource_subdata` 已把 level 字节放进 `SEG_STAGE`,但 `ApplyTextureUpload` 只做门与累积、把指针丢掉,Espryt 同步时从 client 的 `MipmapStorage` 重读纹素;纹理对象的形状与脏区也直接读前端对象 | 丢指针 `MobileGL/MG_Pipe/PipeApply.cpp:989-1008`;重读 `DirectGLES/Managers.cpp:7315`(`MapMipmapData`)、`:6940`、`:7129`、`:7198`;形状 `:7314`、`:7332`、`:6878-6880`、`DirectGLES/DirectGLES.cpp:8502-8507`、`:8528-8529`;脏区 `DirectGLES/Managers.cpp:7360`、`:7371` | barrier + 同地址空间 | **无**:整个纹理家族不在 `FieldOwnership.def`,`rsp` / `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` 都看不见 | server 端纹理 staged shadow(buffer `StagedShadowStore` 的纹理半边);`SyncMipmapsToBackend` 改读它,形状与脏区读描述符 | -| T2 | S→C 读写 | client 的 slot 分配器 `MGPipeSlots()`:mip / CopyTex / twin 创建时按前端 `GetLifetimeId()` 在 client 分配器里查找甚至铸造 | `DirectGLES/DirectGLES.cpp:8087` → `DirectGLES/SlotTables.h:392-400`(`HandleOf` → `FindByLifetimeId`);铸造 `DirectGLES/SlotTables.h:232`(`GetOrCreate`)、`:418`、`:428`;`DirectGLES/Managers.cpp:2708`、`:4293`、`:5627`;`DirectGLES/DirectGLES.cpp:6706`、`:8185`、`:8564`、`:8659` | barrier;mip 只 Find 不 mint | 无(`MOBILEGL_ASSERT` 在 INFO 构建失效) | 记录已带句柄(`MGPMipPlan::Res`、`MGPCopyFromFramebuffer::Dst`、`MGPBlit::ReadFbo/DrawFbo`)→ `GetOrCreate(MGPipeHandle)`(`DirectGLES/SlotTables.h:278`) | -| T3 | S→C 读 | 具名 blit:client 临时改绑自己的 read / draw 绑定槽,server 经 `MGB_CTX` 读绑定槽 | client `MobileGL/MG_Remote/Client/EmitTables.cpp:741-786`;server `DirectGLES/DirectGLES.cpp:7782-7785` | barrier + RAII 恢复 | `rsp`(`GetFramebufferBindingSlot`) | 记录里的 `ReadFbo` / `DrawFbo`,sink 侧按句柄解析(`MobileGL/MG_Remote/Server/PipeApplier.cpp:228-244`) | -| T4 | S→C 读 | `CopyTexImage2D` / `CopyTexSubImage2D` 经纹理单元绑定槽取目的纹理 | `DirectGLES/DirectGLES.cpp:8561-8563`、`:8656-8658` | barrier | `rsp`(`GetTextureUnitObject`) | 记录里的 `Dst` 句柄 | -| T5 | S→C 写 | Magma 生成 mip 直接写 client 的 level 存储(Espryt 的重推导臂没有 Magma 对应) | `DirectVulkan/Renderer/VulkanRenderer.cpp:1562-1590`(调用 `:11317`) | barrier | 无 | 与 GLES 同形:只按描述符验证 | -| B1 | S→C 读 | `BufferObject::HasDefinedContent()`,每个 ensure 的 draw 都读 | `DirectGLES/Managers.cpp:3147-3148`(`EnsureBufferResourceForHandle`;`:3141-3146` 自述为债) | barrier | 无 | 描述符旁的 live-content 位,client 发布 | -| B2 | S→C 读 | `HandleOfBuffer` 每次 ensure 读前端 `GetLifetimeId()` | `DirectGLES/Managers.cpp:2705-2716` | barrier + memo | 无 | 记录里的句柄 | -| B3 | S→C 读 | 旧 buffer 臂(`MappedData()` / `IsMapped()` / `GetChangeSerial()`)在 split 构建里位 7 清零时可达,arm resolver 只打一行 `MGLOG_D` | `DirectGLES/Managers.cpp:2586-2620`;臂体 `:1012-1022`、`:1119-1121`、`:2944-2949`、`:3281-3327` | 同进程 | 无 | 有传输时具名拒绝(同 `Fatal{PipeLegacyMemosDisabled}` 的形状) | -| B4 | S→C 读写 | XFB:`SharedPtr` 跨整个 span 持有,结束时 `WritebackFromBackend`,scatter 对 client shadow 读改写 | `DirectGLES/DirectGLES.cpp:950`、`:1060`、`:1077`、`:1081`、`:1172`、`:1185` | barrier | 无 | `OnBufferWriteback` / `OnXfbScatterReady` 事件——P9 的题材,P5c 只记账 | -| R1 | S→C 写 | 反向通道是 apply 线程直接调进 client 的 `MG_State`:`OnBufferWriteback` 把**裸指针**塞进 `MGPBlobRef.Offset`,client 侧再转回指针;consumer 会拒绝任何真实段 | 生产 `DirectGLES/Managers.cpp:2339-2343`(`Ops_H_Readback`);消费 `MobileGL/MG_Impl/Pipe/ResourceTracker.h:553-572`(拒绝真实段 `:560-564`) | 同地址空间 + barrier | 无 | `SEG_EVENT` `kEventBufferWriteback`:consumer 已在(`MobileGL/MG_Remote/Client/ClientSession.cpp:176-267`),**全仓零 producer**(`MobileGL/MG_Remote/Server/ServerSession.cpp:484-492` 无调用者) | -| R2 | S→C 写 | `OnGpuWritten` 直接调进 client tracker;Magma 更是绕过回调直接 `MarkGpuWritten()` | Espryt `DirectGLES/Managers.cpp:2729-2752`,喂入 `DirectGLES/DirectGLES.cpp:568`、`:616`、`:2601`;Magma `DirectVulkan/Renderer/UniformManager.cpp:1075`、`:1231`、`DirectVulkan/Renderer/VulkanRenderer.cpp:11618` | 同地址空间 | `MGLOG_E_ONCE` | `SEG_EVENT` `kEventGpuWritten`(consumer `MobileGL/MG_Remote/Client/ClientSession.cpp:227-236`) | -| R3 | S→C 写 | 默认 framebuffer 附件(`pDefaultFramebufferInfo`)由 server 在 surface 创建时写 | `DirectGLES/DirectGLES.cpp:11519-11620`;Magma `DirectVulkan/Renderer/SwapchainObject.cpp:276-335` | barrier;罕见 | 无 | `SEG_EVENT` `kEventSurfaceChanged`(consumer stub `MobileGL/MG_Remote/Client/ClientSession.cpp:242-253`) | -| R4 | S→C 写 | sticky forward:`RecordError` 写 client 错误队列;`InvalidateCompileEnv` 写 client 编译环境(在 stamped verb 之外静默 no-op) | `MobileGL/MG_Impl/Pipe/PipeFill.cpp:1753-1756`、`:1764-1772` | barrier | `rsp`;strict 下 Fatal | `RecordError` → `OnGlError` 事件(顺序保证是 P9 的);`InvalidateCompileEnv` 的活已由 R-12 caps 重发布完成(`MobileGL/MG_Remote/Client/CapsMirror.cpp:78-80`),删 forward | -| R5 | — | `MGPipeCallbacks` 十个回调只装了两个,且都是 apply 线程内联调用;`SEG_EVENT` 段已创建、映射、握手公告、每个 reply verb 后排空,但没有任何生产者 | 安装 `MobileGL/MG_Impl/Pipe/ResourceTracker.h:606-613`;段 `MobileGL/MG_Remote/Server/ServerSession.cpp:285-301`;排空 `MobileGL/MG_Remote/Client/ClientSession.cpp:836` | — | 溢出 latch 与 `eventDropped` 已建未用 | 本身就是载体 | -| C1 | S→C 读 | Magma 四处直接读 client 镜像 `pActiveBackendObject->GetDynamicParameters()`(Espryt 已改走 server 自己的 backend);Espryt 的格式表回落臂在 server backend 为空时读 client 镜像 | `DirectVulkan/DirectVulkan.cpp:713-715`;`DirectVulkan/Renderer/VulkanRenderer.cpp:667-676`;`DirectVulkan/Renderer/VertexInputStateFactory.cpp:249-250`、`:502-503`;`DirectGLES/Utils.cpp:50` | 镜像值等于快照 | 无 | server 自己的 `Backend()->GetDynamicParameters()`;回落臂拒绝 | -| G1 | shared | `gPipeInputs`:client 残余填充写、applier 写、server 的 36 行 BARRIER-PULLED 读 | `MobileGL/MG_Backend/MGPipe/PipeInputs.h:806`;填充 `MobileGL/MG_Impl/Pipe/PipeFill.cpp:2798`;applier `MobileGL/MG_Pipe/PipeApply.cpp:1415-1560`;stamp `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp:103-132`;`rsp` `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp:72` | **只靠 verb barrier** 保证单写者 | `rsp` + strict Fatal + poison | 值类行(`GetActiveTextureUnit`、`GetMaxTouchedTextureUnit`、`GetTouchedBufferBindingPointCount`、`GetCurrentVertexAttribute`、XFB 六项、三个世代)→ 每 verb 一条残余值记录或按 `Coverage.def` 由 server 自答;**对象类行**(`GetBoundVertexArray`、`GetProgramForDraw/Dispatch`、`GetTextureUnitObject`、`GetImageTextureBinding`、`GetFramebufferBindingSlot`、`GetBufferBindingSlot/Point`、sticky `GetTextureObject` / `GetProgramObject`)返回前端对象指针、无法序列化,留 P3b/P4b/P7/P8 的 twin 表 | -| G2 | C→S 写 | GL 线程在 `FreshlyPrimed` 时整体 `MGPipeApplierReset()` server 的 `g_applier` | `MobileGL/MG_Impl/Pipe/PipeFill.cpp:2654-2656` → `MobileGL/MG_Pipe/PipeApply.cpp:1202-1299` | barrier 的后置条件(validate 时无在飞记录) | 无 | make-current 边上的 `applier_reset` 控制记录 | -| G3 | C→S 写 | 六种前端对象死亡:栈上结构体指针经 mailbox 送到 apply 线程(framebuffer 没有 wire delete opcode) | `DirectGLES/Managers.cpp:206-228` | 阻塞 + 同地址空间 | 无 | `object_death` wire 记录 | -| G4 | C→S 控制 | 十二个 `Server*` EGL forwarder 经 one-slot mailbox 传**函数指针 + `void*` 栈局部** | `MobileGL/MG_Remote/Client/BackendObject_Remote.cpp:125-264`;邮箱本体在 fc 前的 HEAD 上是 `ServerLoop.cpp:533-568`(PumpControlRequest)+ `:645-707`(RunOnApplyThread)——本行原引 `:543-598`,f0-egl F9 修正 | 阻塞 | `Fatal{ApplyThreadNotRunning}` | **已由 P5f 包 fc 帧化**(原记 P6):`SurfaceControlFrame` 纯值帧走同一阻塞通道(`Server/SurfaceControlFrame.h`、分发 `ApplySurfaceControlFrame`),wire 编解码与具名拒绝在 `Protocol/SurfaceOpCodec.cpp`;报告 `notes/p5f/fc-report.md` | -| G5 | C→S 读 | `ServerLoop::OnApplyThread()` 每条 routed 记录读;bring-up 直接读 `loop.Backend()` / `MGPipeGetResourceOps()` | `MobileGL/MG_Remote/Client/WireTables.cpp:71`;`MobileGL/MG_Backend/Init.cpp:100`、`:145`、`:164-170`、`:247` | 同进程 | — | client 本地角色标志;caps 快照的 cap 位(P6 顺手) | -| G6 | shared | 前端地址 / lifetime-id 键控的 Espryt twin registry;持前端裸指针的 memo(`ResolvedDrawBuffers::Entry::frontend`、三张 texture sync list);server 读 `pDefaultFramebufferInfo`;`g_rawDepthFetchSamplerState`;`ScopedDefaultUnpackState::s_synced` 从不按 context 世代重置 | `DirectGLES/Managers.h:309-377`、`:1185`;`DirectGLES/DirectGLES.cpp:61-62`、`:1958-1987`、`:2846`;`DirectGLES/Managers.cpp:5806-5812` | barrier / server 私有 | — | 历史移交给 P3b/P4b / P6;transport registry 与 `s_synced` 世代问题已由 P5f fr / fs 收口 | -| A1 | — | barrier 的 client 半边断言从未接线:`InBarrierWait()` 零调用点,只有 `ApplyThreadIsInsideApplier` 在 emit 时检查 | `MobileGL/MG_Remote/Client/ClientSession.cpp:880`、`:748-753` | — | — | 接上,或删掉契约里的这句声明 | - -已确认 wire-clean、不进 P5c:buffer 全家族(`Ops_H_*` 只拿句柄 + 记录 + `SEG_STAGE` 字节,`StagedShadowStore::Adopt` 复制,`Fatal{StageSnapshotTooNarrow}` 六处);`resource_respecify` 初始字节双向拒绝;persistent map 仿真档按块过 `SEG_STAGE`,`Fatal{RoleViolation}` 挡住 apply 线程;`CreateShaderState` 归档是一个 blob;caps 的格式表与 renderer 字符串走 `CapsSnapshot` 控制帧(`GetCaps` 记录本身已死,只是卫生项);tight ReadPixels;`DrawVbo` 用户索引;fence 家族;`gMGPipeSegmentResolver` 与 `gBackendFunctionsTable` 的角色划分。 - -### 包与顺序 - -| 包 | 内容 | 关闭的行 | -|---|---|---| -| **c0c** 契约 | `MG_Remote/CONTRACT-P5C.md`:纹理 staged shadow 的所有权与覆盖规则;`SEG_EVENT` 记录与 blobref 约定(`Seg = kSegEvent`);sink / twin 按句柄解析的规则;`applier_reset` / `object_death` 记录;角色守卫的语义(有传输时 apply 线程触前端对象表面、GL 线程触 server 状态表面都是 `Fatal{RoleViolation}`);残余值记录的字段表;把纹理家族补进 `FieldOwnership.def` | — | -| **tx** 纹理 | `StagedTextureStore`:`ApplyTextureUpload` 采纳 `SEG_STAGE` 字节到 server 侧 level shadow;`SyncMipmapsToBackend`、形状读、脏区改读描述符与 shadow;Magma mip 改按描述符;`MOBILEGL_IPC_AUDIT` 的 `0xDD` 覆盖纹理 | T1、T5(T2 的 mip 半边随之) | -| **ev** 反向通道 | `ServerSession::PublishEvents` 接三个 producer(writeback / gpu-written / surface-changed),consumer 的段臂改为解析 `kSegEvent`;Magma `MarkGpuWritten` 改走回调;`RecordError` 进事件(顺序仍 P9);删 `InvalidateCompileEnv` forward;溢出策略沿用 `ARCHITECTURE.md` §11.7 | R1–R5 | -| **hd** 句柄解析 | blit / mip / CopyTex / `HandleOfBuffer` 的 sink 与 twin 按记录里的句柄解析(`GetOrCreate(MGPipeHandle)`),server 不再触 `MGPipeSlots()`;`HasDefinedContent` 改读描述符的 live-content 位;Magma 四处 caps 读改 server backend,`Utils.cpp` 回落臂拒绝 | T2、T3、T4、B1、B2、C1 | -| **ct** 控制记录 | `applier_reset` 与 `object_death` 两条 wire 记录(framebuffer 首次有 delete opcode);mailbox 只剩 EGL forwarder 给 P6 | G2、G3 | -| **rv** 残余值 | 值类 BARRIER-PULLED 行改为每 verb 的残余值记录(或 server 自答),`gen_pipe_field_ownership.py` 把它们改成 RECORD-SUPPLIED;对象类行保持 BARRIER-PULLED 并逐行标注归属阶段 | G1 的值类半边 | -| **gt** 门 | 角色守卫(两层 `Fatal{RoleViolation}`,仅 split 构建、有传输时生效);`InBarrierWait` 接线;旧 buffer 臂传输拒绝;`rsp` 按帧进 stats 行并在四条 A/B trace 上测得;CI 加一条 `MOBILEGL_IPC_STRICT_ERRORS=1` + 角色守卫的 `integration-split` 车道 | A1、B3,以及所有行的红一次证据 | - -顺序:c0c → tx / ev / hd 并行 → ct / rv → gt 收口。每包自带 red-once(R-16),阶段末一次 Codex / Kimi 审查(ID-66)。 - -### 出口门(E-P5c) - -1. **角色守卫下全绿**:守卫开启时 `integration-split`(107)、broad inproc 车道对 `348d22a4` 普查零回退、79 trace 无新增首阻塞;关掉任一层守卫必须能在一条具名用例上变红。 -2. **纹理字节不再回读 client**:`MOBILEGL_IPC_AUDIT=1` 对纹理 staged 字节的 `0xDD` 填充在四条 A/B trace 上无失败;把采纳改回丢指针必须变红。 -3. **`SEG_EVENT` 有生产者**:三条事件的往返单元与集成用例;排空点上 `eventDropped == 0`;把 producer 改回裸指针必须变红。 -4. **`rsp` 分类完成**:四条 A/B trace 每帧 `rsp` 已测;剩余读全部是对象类行,名单由 `FieldOwnershipTest` 钉住,值类行为 0。 -5. **Redmi 四臂复测**(记录,不设门):writeback / gpu-written 改走事件后 barrier tax 重新记录。 -6. G1 0/0/0/0、G2 / G14、G5 照旧。 - -### 历史移交项(P5c 当时不碰) - -P5c 当时移交的对象类 BARRIER-PULLED、transport registry 前端键、EGL 控制面、`s_synced` / `g_syncedRenderStateParameters` 世代与反向通道跨角色访问,已在 P5f 收口。P9 的异步化/排序、P8/P9 的协议广度与仿真功能仍按各阶段推进;不要把这份历史清单重新当成 P6 的帧化或静态量施工单。 - ## 里程碑 -- **P2 出口(2026-09-08):GO/NO-GO 判定继续**——五部分门全绿,逐线程 CPU 代价约 +10% 被接受;tracker 绝对 ns 上限降为记录项。 -- **P3a / P4a 出口(2026-09-08)**:各 1 天,远低于 27 / 39 天的再基线绊线,未触发重定基线。 -- **P5 出口(2026-09-16)**:缩减路径首个 IPC 帧;最终全门在 E3(a) 停止;红米四臂的 split 均停在索引 draw,barrier tax 未测(归 P5b)。 -- **P5b 出口(2026-09-16,已达成)**:主机全门 `348d22a4` complete;79 trace 72/6/1;设备源头 `82683d4a`(APK `p5bcodex2`)Redmi 正确性 8/8——四条 A/B trace 双后端首次在设备上 inproc 渲染;barrier tax 首测 split−push 逐线程 CPU p50 +5.9% – +18.2%。 -- **P5f 出口(2026-09-20)**:双块和 strict 空棘轮、逐帧 rsp=0、零 BARRIER_PULLED、主机全门、异模型族审查修复和 Redmi 六 clean-boot 臂全部完成;P6 前提解除,实施未开始。 -- **CI / inproc 收尾出口(2026-09-21)**:`feat/disaggregated` 主产物拆分编译 + inproc 主验收全绿——unit 2328 零失败、`integration-gpu` 双 transport 各 1455 零失败、retrace 77 例 monolith 77/77 且 inproc 的唯一间歇项(`bsl-esc-menu-854` DirectVulkan,lavapipe 固有 texture-handle 注册停顿越过旧 30 s barrier 预算)在预算修正为 120 s 后隔离 10/10 绿、18 项 CI 门全绿、双 ABI APK 与 AVD 生命周期验证通过;合入头 `e1bf3677`,最终矩阵以远端 CI 为准。 -- **P6 收官(2026-09-22)**:a6/c6/lk/so/sm/cp/hs/dl/st/t6 十包于 2026-09-21 合入 `feat/disaggregated`(头 `77cbd176`);三种传输(monolith/inproc/spawn)桌面与真机两进程 retrace 逐像素对上 golden;device-lost 闩把杀 server 从 123 s 错误 `BarrierTimeout` 变 5.7 s 正确 `DEVICE LOST`;`Session::Fail` 漏斗把裸 abort 93→4。出口门 1–6 ✅;**门 7 / 8 于 2026-09-22 采齐**:真机 reboot-clean 同热窗口配对 A/B 六臂 18/18 runner 退出 0、36/36 `PINNED`、0 `Fatal`,inproc vs spawn 的 client CPU p50 差 −0.15%(第二会话 +0.16%)——**判读 tie**(臂内散布 0.2–4.2%),monolith 基线约贵 30%;门 8 三数齐(门铃 A/B tie、`SEG_STAGE` 字节/帧、分块后记录/帧);G1 符号 0/0/0/0 且 `.text` 逐字节相同。**P6 就此收官,未设性能门**(只记录)。逐项见 `CURRENT_STAGE_PROGRESS.md`。 -- **终局改判(2026-09-22)**:AVF pVM / `AF_VSOCK` 终局撤回,改为 TCP 跨机跨平台 + 控制面 × 数据面两轴传输栈;P6.5 上关键路径,Ph 前置于 P12 的非 loopback 监听;P6 核出三项漏项(server 进程 `PipeStats` 结构性零——收尾中已修、184 棘轮门、hs 的三行契约未落地)与 `CURRENT_STAGE_PROGRESS.md` 里一条错误的 S6 陈述(已改);class-C 已归零到两个(`SetSwapInterval`、`DeleteTransformFeedback`)。 -- **P7 wave 2 落地(2026-09-22)**:X1 / F1 / C / A / B / D1 / D2 / D3 / B2 / B3 十包合入 `feat/disaggregated`(`origin@b089e0de`,B3 在集成树上待推),每包八车道全绿、G1 恒等、每包一条两进程臂 red-once、每包一次 fable 聚焦审查;子系统分母 **6/9** 于 B2 落地时越过 40% 中点,不触发重定基线。真机 p7w4:门 3 34/36 与 monolith 同;OpenRA 的两个历史点值(0.976494 / 0.988998)被证明是同一帧一次 draw 的顶点前缀撕裂——wire 臂的 frame-serial floor 不健全,B3 关闭;bsl-esc-menu 是 server 死 VkBuffer 无界累积,M2 + r2 已落地(主机 spawn server 825 → 546 MiB;真机验收待 p7w6)。 -- 仍是方向、不据此伪造新日历:全功能 split(P8 之后)、纯度门在非 verify 构建上转绿(P13)。 +- **2026-09-08**:P2 出口 GO/NO-GO 判定继续;P3a、P4a 各 1 天,未触发重定基线。 +- **2026-09-16**:P5 出口(首个 IPC 帧,全门停在 E3(a));P5b 出口(Redmi 正确性 8/8,barrier tax 首测)。 +- **2026-09-17 / 18 / 19**:P5c 同日收官;P5d 三轮;P5e 收官(run-ahead 武装)。 +- **2026-09-20 / 21**:P5f 收官;CI / inproc 功能补齐收官(`e1bf3677`)。 +- **2026-09-22**:P6 收官;终局改判为 TCP 跨机 + 两轴传输栈;P6.5 第一波落地;P7 wave 2 落地(B3 找到 OpenRA 真机分歧的真因)。 +- **2026-09-23**:P7 收官。 +- **2026-09-24**:P12 子集并入,未收官。 -再基线检查点仅剩一条:**P7 中点完成子系统 < 40% 立即重定基线**(P3a 的检查点发现不了 Magma 特有的超期)。 +仍是方向、不据此伪造日历:全功能 split(P8 之后)、纯度门在非 verify 构建上转绿(P13)。各里程碑原文在对应的 `notes/<阶段>/README.md`。 -## P5 / P5b 出口记录的债务 +## 仍开放的债务 -| 债务 | 去向 / 当前口径 | +| 债务 | 去向 | |---|---| -| P5 的 27 个普通 inproc wrong-answer(历史计数) | **已结(2026-09-22,`3c80cd62` 同名重跑,`MEASUREMENTS.md` §7.2;inproc 与 spawn 逐名相同)**:原「22 → P4b/P7 texture readback」→ **19 绿**(14 layered、3 packed depth/stencil、2 framebuffer recycle),余 **3 条 framebuffer recycle 由错答变为具名停止**——三条都是 `integration-monolith-control` 里 `MOBILEGL_PIPE_PUSH=0` 的对照臂(DG `Legacy` → `Fatal{PipeLegacyMemosDisabled}`;DV `Legacy` / `AbaControl` → `Fatal{UnmigratedVerb, "Magma:clear-framebuffer-record"}`),旋钮在 split 下没有臂,不是 readback 欠账;原「3 → P7 query」→ **3 绿**;1 inspection(`TextureParamsWithoutASamplerView`)→ **绿,「待核」关**;1 FBO/RBO delete → 绿。同一重跑的 1267 名合并 lane 普查 811/203/62/191 → 1051/172/37/7,44 条非绿全是钉了非默认 `MOBILEGL_PIPE_PUSH` 的对照臂 | -| `rsp` residual inputs | **P5f 已关闭残余读取**:字段归属 41 record / 6 derived / 0 barrier / 16 fatal;strict 与双块 marker 表均为空,双后端逐帧 RSP 门 2 PASS / 0 skip,Redmi 36 个统计窗口 rsp=0。P5e 的“rsp 只计被采纳设障 pull”是历史计数规则,八对 admitted 的历史观察不再是当前允许集合 | -| `SEG_REPLY` 2 MiB 单槽 payload cap | → **P9**(契约 §10.2.3 改判,ID-47);stream 链路上 reply 本就是消息,cap 变成 P6.5 nd 的 `maxReplyBytes` 窗口 | -| `GetCaps` 的两个 blobref | 目前不骑 record;一旦运输,必须有 server→client carrier rule,不能套 `SEG_STAGE` | -| PACK-PBO readback | P5 曾具名拒绝;`70df57bb`(2026-09-20)起 **client 侧已实现**(bounce 紧凑 reply + 按 PACK 步长逐行 `UploadSubData`,`EmitTables.cpp`;`glGetTexImage` 同形且按 `MaxReplyBytes` 分块);剩余只是 fire-and-forget(`MarkReadPixelsPackBuffer` 全仓无生产调用者)→ **P9**(ID-57) | -| ABI fingerprint | **已由 P6.5 wf / nd 落地(`fe28bdb5`)**:`wireFingerprint` 布局摘要 + `buildFingerprint` 拆分(只在 `Dial==Fork` 或 `MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 下比较)、`LinkTerms` 四个尺寸由 server 陈述、`DynamicBackendParameters` 定宽;`CONTRACT-P6.md:666` 仍把定宽记在 P7 名下,待改 | -| 默认 32 MiB staging | 目标负载单次 128 MiB 上传装不进默认 stage;**内容侧分块已落地**(buffer 范围走查 `1e7c372e`、纹理整宽 slab `9469d48e`,预算 `MGPipeStageChunkBytes()` = 默认 segment/4 = 8 MiB),故普查 / Redmi 的显式 256 MiB profile 不再是这两条路径的必需;未接入分片的 record 类型与专用 carrier 仍归 P8(开放问题 11) | -| P5b 的 inproc 依赖 | 历史的 scoped client binding、mip registry 查询与 FBO death mailbox 已经 P5c/P5f 改为记录、句柄和控制/事件路径;P6 核验并装配已有载体,不再重做这些迁移 | -| 未迁移与仿真路径 | **class-C 已归零到两个**(2026-09-22 核):`EmitTables.cpp` 的 `MGR_UNMIGRATED_*` 列表全空,只剩手写的 `SetSwapInterval`(P10)与按裁定无行的 `DeleteTransformFeedback`(P9);契约 §11 / SPAWN-PLAN §8 的"511 class-C entries"是 P5 joint 树上的旧普查(`notes/p6/census-classC.md`),不是当前数;~~client vertex arrays~~(已实现:`beba0256` 起 wire 复制到 owned buffer 并保留 baseVertex/baseInstance/drawID,本轮补齐 monolith 臂 `62bfe461..e258a822`)、multi-draw client indices、RGB 三通道 CPU mip、renderbuffer copy endpoint、未绑定 named clear、`texture-remint-pull` 仿真保留具名拒绝 → P8 / P9;Magma split compute / image 的“82 个错答”是 P5b 历史普查计数,不是当前失败清单;P5f 后续已补应用 buffer consumers 与 Android 旋转 blit,Magma inproc 已能运行所测 MC 世界;后续 run-ahead 已完成([报告](notes/p5f/magma-runahead.md));XFB、部分不对齐范围、placeholder/native-format 及进一步性能工作仍归 P7([报告](notes/p5f/magma-inproc-fix.md)) | **P5e 补 → CI 收尾更正**:client vertex arrays 曾是 run-ahead 下的具名拒绝(ID-82)与预期红车道 `integration-clientarrays-split`(ID-134);`beba0256` 起已实现,`347bb90c` 已把该车道翻转为**硬绿门**并纳入主 GPU/split 标签,本轮补齐 monolith 臂(`62bfe461..e258a822`);staging 仍归 P8 -| E2 wire 内容控制 | draw-drop 是 OpenRA 的有效控制(758 draws,SSIM 0.000036);clear-drop 被全屏 overdraw 掩盖,不能作控制 | -| max record bytes | 实测 reduced / OpenRA `maxrec=784 B`,默认 cap 4 MiB;只代表所测负载,后续索引 / indirect 尾仍须记录 | -| 树外脚本 | 普查跑器、`wsl_p5_gate.sh`、Redmi 定频行都在 `~/w7/notes/`,git merge 不会传播 | -| 临时 CI trigger | 合入 dev 前删除 `test.yml` / `apk.yml` 的 `feat/disaggregated` TEMPORARY trigger | +| `SEG_REPLY` 2 MiB 单槽 payload cap、PACK-PBO 回读的 fire-and-forget | P9(ID-47、ID-57);stream 链路上 cap 变成 `maxReplyBytes` 窗口 | +| 未接入内容分块的 record 类型:program archive 与 `draw_vbo` range 尾 | P6.5 sl 与 P8 共用的分片(开放问题 11) | +| class-C 余项与仿真路径:`SetSwapInterval`(P10)、`DeleteTransformFeedback`(P9);multi-draw client indices、RGB CPU mip、`texture-remint-pull` 等具名拒绝 | P8 / P9 | +| `GetCaps` 的两个 blobref | 一旦运输,必须有 server → client 的 carrier rule,不能套 `SEG_STAGE` | +| E1 对照的重定义(ID-122) | 无主(开放问题 24) | +| `MOBILEGL_IPC_IDLE_EXIT_S` 未解析;`CONTRACT-P6.md` 仍把 `DynamicBackendParameters` 定宽记在 P7 名下(已由 P6.5 wf 落地) | 文档 / 小修 | +| P12 文档债:`MG_Remote/CONTRACT-P12.md` 未写;`protocol.fbs` 的窗口注释仍是旧说法 | P12 收尾 | +| 树外脚本:普查跑器、`wsl_p5_gate.sh` 等在 `~/w7/notes/`,git merge 不会传播 | 需要时入库 | +| 临时 CI trigger | 合入 `dev` 前删除 `test.yml` / `apk.yml` 的 `feat/disaggregated` 触发器 | + +已关闭的历史债(P5 的 27 个 wrong-answer、`rsp` 残余读、P5b 的 inproc 依赖、ABI 指纹、默认 staging 装不下 128 MiB 上传、184 符号棘轮 CI 门)及当时的完整债务表见 [`notes/p5b/README.md`](notes/p5b/README.md) 末节。 ## 开放问题 -P0 已回答的不再列出(spike A 的域、spike B 的分档、`posix_spawn` 不可用、OOM 探测惯用法、`GetInteger64i_v`/`GetProgramiv` 退役、D21 与 `RenderbufferObject` lifetime id、动态 accessor 基线)。 +每题的完整陈述与已答部分的出处在 [`notes/OPEN-QUESTIONS.md`](notes/OPEN-QUESTIONS.md)(编号稳定)。 -1. **client 侧 dirty 走查的真实每 draw CPU 代价。** 已答(P2–P4a):推送没有在拉取基线之下净减少;Release 下 P2 边界 +6–12%,P3a 在 VAO 切换密的 rd12 上再加 +17–19 pt,P4a 在 Espryt 上 +3.4–5.7 pt(`MEASUREMENTS.md` §3–§5)。用户 2026-09-08 接受,性能自此只记录。 -2. **真实语料上纹理重铸拉取的发生率。** 已答(P4a):可忽略,保留 LRU 维持默认 0——79 例 × 两后端 780 个统计窗口里共 2 次(`iris-photon`、`iris-derivative` 各 1,只在 DirectGLES)。但 P5b 下这条路径是具名 Fatal,正是这两条 trace 加 `create-indirect` GLES 的首阻塞(P9)。 -3. **spike B 的 `untrusted_app` 域复核。** 两台设备的分档在 `shell` 域测得;从应用进程再跑一次 `extmem_probe`(spike A 的 exec 钩子已可用)。P11 前做。**2026-09-22 降级**:只对同机臂的 T0 / T1 有意义,stream 数据面不需要。 -4. **渲染状态的 wire 粒度。** 已答(P2):16 个边界 / 15 个 chunk,7 pipeline 396 B + 8 dynamic 772 B;CSO LRU 64 暂定,P13 重调。 -5. **无存储的 capability。** 已答(P2):`FramebufferSrgb`、`DepthClamp`、`TextureCubeMapSeamless` 三个都补了真存储,`sizeof(RenderStateParameters)` 仍 1168。 -6. **具名 UBO host payload 的形状(D-B8)。** Magma 在 26.3 世界每帧重打包 331 KB 具名 UBO 字节,Espryt 为 0。要么冻结第二变长尾形状,要么走备选(Magma 直接描述符绑定常驻 `VkBuffer` range,独立 `dev` PR)。P7。 -7. **`MG_Util` 的切割缝。** server 需要 SPIRV-Cross / ESSL 转译缓存 / 格式处理器 / POST 探针,client 需要 glslang 与反射层;`MG_Util` 内部是否有干净的 Transpile-vs-Reflect 缝未审计。P7。 -8. **一份反射归档能否服务三个消费者。** Espryt 那一半已答(P4a):能且不需要复制。Magma 的第一个消费者已由 P5f fm 答:`MagmaProgramSource` 全部从 wire `ProgramArchive` 取,无前端对象、无复制。第二个(storage block 表)今天是**否**且变差了:split 下 `MagmaProgramSource::EnsureStorageBlocks` **每 draw** 重跑 SPIRV-Reflect(monolith 臂 `DirectVulkan.cpp:177-200` 至少有持久缓存);答案是往 `LinkArtifacts` 加有序 `storageBlocks`(数据已在 `blockReflection` 里),两个消费者都改读它。**已答(P7 wave 2-C,`dce9953d..cf7ca59f`)**:`storageBlocks` 入归档、两个消费者改读、每 draw SPIRV-Reflect 4→0;`g_programResourceCaches` 的 monolith 半边归 P13(G1)。 -9. **viewport-array 回放能否塞进一次 `draw_vbo`**:各遍之间观察到的状态是否与今天一致未验证。P8。 -10. **`ResidentSubData` 的不对称。** 已答一半(2026-09-22 核):**Magma 的实现已补**(`VkBufferManager.cpp:123` monolith 表、`:148` wire 表,`fff9d639f`)。**仍未答:`kCapResidentSubData` 从未被 server 发布**——`MG_Backend/Init.cpp:192-219` 是全树唯一的 `SetCapabilityBits` 生产调用点,只 OR 了 XFB、三个 query 位与 run-ahead 位;于是 `PipeFill.cpp:848` 在 split 下恒 false,**两个后端在拆分模式下都退回 `BufferObject.cpp:711-716` 的就地 memcpy**,Magma 的实现在 split 里是死代码。**已答(P7 wave 2-C,ID-P7-20)**:`InitServerRoleCommon` 按 server 自己的 wire 资源表发布 `kCapResidentSubData`,镜像收到即验收;白盒「发出的是 opcode 49」在 P11 采纳档落地前不可达(§5.4 债)。 -11. **`SEG_STAGE` 的上限。** 内容侧已答(2026-09-20):**分块已实现**——buffer 范围走查(`1e7c372e`)与纹理整宽 slab(`9469d48e`)按 `MGPipeStageChunkBytes()`(默认 32 MiB 的 1/4 = 8 MiB)切成多条 `resource_subdata`,所以单次 128 MiB 上传不再需要显式 `MOBILEGL_IPC_STAGE_MB=256`;单片仍超 arena 或该 record 类型未接入分片时仍是 `Fatal{RingOverrun, "SEG_STAGE"}` 兜底。**仍未答(2026-09-22 收窄)**:a6 §6 实测没走查的只剩 **program archive**(唯一无界 blob)与 **`draw_vbo` 的 `NumDraws*12` range 尾**;宿主索引 span 无 producer(划掉),`set_global_constants` 有走查。二者的分片是 P6.5 sl 与 P8 共用的同一件事,stream 发送窗口对着它们定尺。MC in-world / Create 的占用分布仍未量(门 8 已给出两条负载的字节/帧与记录/帧,逐 blob 分布只在 integration 车道取到)。 -12. **P13 之后 split-only 渲染 bug 的 server 侧第二意见。** verify 构建 + recorder 只覆盖推送内容,不覆盖后端对它的解释。 -13. **烘焙后的内部 shader 能否在没有活 `ProgramObject` 的情况下表达 uniform location 与 UBO 布局。** 颜色 blit 已答(P5f fv:`WireColorBlit.{vert,frag}` 用 push constant,`WireColorBlitSpirv.h` 签进树);窄化到深度 mip 程序(今天在 split 下被编译掉、其 split 路径是具名 Fatal)与直通 TCS(`ProgramFactory.cpp:4060-4262`,第三个未盘点的内部 shader 生成器)。**已答(P7 wave 2-B / B2)**:深度 mip 烘焙 (A)(D)(`WireDepthMipmap.*`,push constant,`MOBILEGL_BAKED_INTERNAL_SHADERS` 新鲜度门表驱动)与 (B′)(disaggregated 构建的 monolith 臂改用烘焙模块)落地;multisample resolve 第三套烘焙 shader 随 B2;直通 TCS 仍未盘点。 -14. **推送模型改变哪些按拉取模式调过的缓存命中率。** 幸存者容量在 P13 重调。一条线索:设备上 26.3 Espryt 的 `sve` ≈ draw 数(每 draw 重发一次 sampler-view 集合),桌面只有 ~0.07/draw;列入 P3b/P4b 优化清单。 -15. **monolith 的 `*IndirectCount` 不调 `SyncGpuWrites()` 是不是潜在缺口。** 独立 `dev` 问题,拆分不得借机顺手修。 -16. **索引宿主镜像的实际内存占用。** MC / Sodium / Iris 语料里 element-array buffer 总量未测;若显著超 64 MiB,退化路径的频率与代价必须实测。P8。 -17. **`create-indirect` fixture 在 Adreno 830 上的失败**是 `dev@81b17c0b` 就有的(基线 APK 复现),不是本分支造成;`rd12` + Magma 在两台 Adreno 830 上的 `scudo::reportMapError` 崩溃同样是 `dev` 侧。两者排除在设备 A/B 之外、留在桌面语料里;P8 要在 `create-indirect` 上断言 `roundtrips-per-frame == 0`,所以 `dev` 的修复在别人的关键路径上。 -18. **split 两侧的负载平衡。** P5e 解锁出来的新问题:VD32(~3550 draws/帧)下 apply 线程 **11-12 ms**、client **15.7 ms**,**client 是瓶颈且打满 ~94%**。把工作从 client 挪到 apply 会直接降瓶颈——这在 lockstep 下毫无意义(两侧串行相加),**run-ahead 才使它成为一个杠杆**。供选:编码侧的 emitter、tracker 走查、持久映射的脏页追踪(`change_protection` 在侧写里占 1.3%)。需先重新侧写:等待消失后尾部各项的分母都变了。排在 P5e 之后;无主,2026-09-22 建议挂在路线图推完后的优化阶段,P6.5 的 2×2 矩阵车道顺带重新侧写 -19. **链路带宽预算与压缩。** TCP 终局下 `SEG_STAGE` 字节/帧对的是 Wi-Fi / USB(量级 30–100 MB/s),不是内存;`ARCHITECTURE.md` §14 的 ~1 MB/帧预算在 60 fps 是 60 MB/s,已贴着 Wi-Fi 上限。是否对 stage 载荷做 LZ4 类压缩、纹理上传是否按内容哈希在 server 侧去重,要等门 8 的分布出来再定。P6.5 契约写下第一行之前必答。 -20. **大单次上传的停顿。** 128 MiB 一次上传在 50 MB/s 上是 2.5 s;内容侧分块解决的是"装不装得下",不是"要等多久"。跨机臂上要么接受并记录,要么给上传做异步化(与 P9 的语义一起想)。不假装解决。 -21. **配对 / 认证的形式。** **已裁定(ID-P7-3,2026-09-22):令牌足够,不做 TLS。** 威胁模型是「任何人都能连的远程代码路径」而不是窃听:令牌常量时间比较、≥16 字节、无令牌只 loopback、数据面以 `Welcome.dataNonce` 绑定到已认证的控制连接、fork 前认证。TLS 作为 P12 的开放项保留。 -22. **client 平台范围。** 默认先 Linux + Android(现有 CI 能验),Windows client 第二批(`ShmSegmentWin32` 在;`pipe:` 具名拒绝作废、TCP 替代),macOS 仍不拆分。这决定 `ARCHITECTURE.md` §15.3 "Windows 机器不是正确性门"要不要改。 -23. **半开连接的检测。** Wi-Fi 掉线不产生挂断事件;dl 的闩取自描述符挂断,TCP 上需要 keepalive / 心跳超时并**由传输层报告为挂断**——这与"绝不取自 apply 超时"不矛盾,写清楚免得重吵。P6.5 ct。 -24. **E1 对照的重定义(ID-122)无主。** 建议并入 P6.5 的门设计:S8 的好路径断言(`SessionFaultCount() == 0`)已是它想表达的形状,缺的是一个能被证伪的负控。 +| # | 问题 | 状态 / 去向 | +|---|---|---| +| 3 | spike B 在 `untrusted_app` 域复核 | P11 前做;只对同机臂的 T0 / T1 有意义 | +| 6 | 具名 UBO host payload 的形状(D-B8) | P7 走了直绑常驻 `VkBuffer` range 的备选,欠一次测量与裁定 | +| 7 | `MG_Util` 的 Transpile-vs-Reflect 切割缝 | 未审计 | +| 9 | viewport-array 回放能否塞进一次 `draw_vbo` | P8 | +| 11 | `SEG_STAGE` 的上限(未分片的 program archive 与 range 尾;MC in-world 的占用分布) | P6.5 sl / P8 | +| 12 | P13 之后 split-only 渲染 bug 的 server 侧第二意见 | 开放 | +| 13 | 烘焙内部 shader 的表达力 | 颜色 blit、深度 mip、MS resolve 已答;直通 TCS 未盘点 | +| 14 | 推送模型改变了哪些缓存命中率 | P13 重调 | +| 15 | monolith 的 `*IndirectCount` 不调 `SyncGpuWrites()` | 独立 `dev` 问题 | +| 16 | 索引宿主镜像的实际内存占用 | P8 | +| 17 | `create-indirect` 在 Adreno 830 失败、rd12 + Magma 的 `scudo` 崩溃 | `dev` 侧既有问题,排除在设备 A/B 之外 | +| 18 | split 两侧的负载平衡(VD32 下 client 是瓶颈,apply 有余量) | 路线图推完后的优化阶段 | +| 19 | 链路带宽预算与压缩 | 等门 8 分布与跨机实测 | +| 20 | 大单次上传在慢链路上的停顿 | 跨机臂上记录,或随 P9 异步化 | +| 22 | client 平台范围(Windows client 第二批、macOS 不拆分) | 开放 | +| 24 | E1 对照的重定义 | 无主 | + +已答:1(client dirty 走查的代价,P2–P4a)、2(纹理重铸拉取率,可忽略)、4(渲染状态 wire 粒度)、5(无存储的 capability)、8(反射归档服务三个消费者,P7 C 补齐 `storageBlocks`)、10(`kCapResidentSubData` 由 server 发布,P7 C)、21(令牌足够、不做 TLS,ID-P7-3)、23(半开连接由 TCP keepalive / 超时在传输层报告为挂断,P6.5 ct)。 diff --git a/docs/Disaggregated/devices/pin-verification-2026-09-07.md b/docs/Disaggregated/devices/pin-verification-2026-09-07.md index 71d18ce9..c502f67f 100644 --- a/docs/Disaggregated/devices/pin-verification-2026-09-07.md +++ b/docs/Disaggregated/devices/pin-verification-2026-09-07.md @@ -8,7 +8,7 @@ |---|---|---|---|---|---| | Redmi M332BF `2f7cbe2e` | SM8750 / Adreno 830v2 | policy6 → 1958400(stock 1017600–3072000) | policy0 → 1555200(stock 556800–2745600) | kgsl `min/max_pwrlevel = 0`;2026-09-11 实测 1050 MHz(厂商 `thermal_pwrlevel` 锁 1),2026-09-16 起实测 1100 MHz | 主动风扇 `/sys/class/xm_power/hw_monitor/pwm_fan` 恒 level 2,用例间降温 <1 min;温度门 `cpuss-0-0` | -与小米 `35d0befa` 同 SoC、同 OPP 表,两台的钉频数值可比;跨钉频口径(1050 vs 1100 MHz)的活动不可比钟频,只有同场配对可比。Redmi 的行目前只在 `~/w7/notes/p2/devices/pin_device.sh`(树外,`~/w7/notes/tools/p5b_codex_redmi.sh` 引用它);树内 `tools/device_bench/pin_device.sh` 只含下面两台,把 Redmi 行并回树内是待办。 +与小米 `35d0befa` 同 SoC、同 OPP 表,两台的钉频数值可比;跨钉频口径(1050 vs 1100 MHz)的活动不可比钟频,只有同场配对可比。Redmi 的行已于 P6 收尾(2026-09-22)并入树内 `tools/device_bench/pin_device.sh`(`2f7cbe2e` 条目;stock 范围与小米不同,GPU 实测钳在 1050 MHz:`thermal_pwrlevel` 写 0 读回 1,见 [`../notes/p6/README.md`](../notes/p6/README.md) §12.7);此前它只在树外的 `~/w7/notes/p2/devices/pin_device.sh`。 ## 2026-09-07 核验:小米 `35d0befa` 与 Oppo `3B159D009VZ00000`(P2 / P3a 使用) diff --git a/docs/Disaggregated/notes/OPEN-QUESTIONS.md b/docs/Disaggregated/notes/OPEN-QUESTIONS.md new file mode 100644 index 00000000..1d16e1a1 --- /dev/null +++ b/docs/Disaggregated/notes/OPEN-QUESTIONS.md @@ -0,0 +1,30 @@ +# 开放问题全文(原 `ROADMAP.md`「开放问题」,2026-09-24 移入) + +> [`ROADMAP.md`](../ROADMAP.md) 只留每题一行的状态;这里是每一题的完整陈述、已答部分的出处与未答部分的判据。编号稳定,引用写 "开放问题 N"。 + +P0 已回答的不再列出(spike A 的域、spike B 的分档、`posix_spawn` 不可用、OOM 探测惯用法、`GetInteger64i_v`/`GetProgramiv` 退役、D21 与 `RenderbufferObject` lifetime id、动态 accessor 基线)。 + +1. **client 侧 dirty 走查的真实每 draw CPU 代价。** 已答(P2–P4a):推送没有在拉取基线之下净减少;Release 下 P2 边界 +6–12%,P3a 在 VAO 切换密的 rd12 上再加 +17–19 pt,P4a 在 Espryt 上 +3.4–5.7 pt(`MEASUREMENTS.md` §3–§5)。用户 2026-09-08 接受,性能自此只记录。 +2. **真实语料上纹理重铸拉取的发生率。** 已答(P4a):可忽略,保留 LRU 维持默认 0——79 例 × 两后端 780 个统计窗口里共 2 次(`iris-photon`、`iris-derivative` 各 1,只在 DirectGLES)。但 P5b 下这条路径是具名 Fatal,正是这两条 trace 加 `create-indirect` GLES 的首阻塞(P9)。 +3. **spike B 的 `untrusted_app` 域复核。** 两台设备的分档在 `shell` 域测得;从应用进程再跑一次 `extmem_probe`(spike A 的 exec 钩子已可用)。P11 前做。**2026-09-22 降级**:只对同机臂的 T0 / T1 有意义,stream 数据面不需要。 +4. **渲染状态的 wire 粒度。** 已答(P2):16 个边界 / 15 个 chunk,7 pipeline 396 B + 8 dynamic 772 B;CSO LRU 64 暂定,P13 重调。 +5. **无存储的 capability。** 已答(P2):`FramebufferSrgb`、`DepthClamp`、`TextureCubeMapSeamless` 三个都补了真存储,`sizeof(RenderStateParameters)` 仍 1168。 +6. **具名 UBO host payload 的形状(D-B8)。** Magma 在 26.3 世界每帧重打包 331 KB 具名 UBO 字节,Espryt 为 0。要么冻结第二变长尾形状,要么走备选(Magma 直接描述符绑定常驻 `VkBuffer` range,独立 `dev` PR)。P7。 +7. **`MG_Util` 的切割缝。** server 需要 SPIRV-Cross / ESSL 转译缓存 / 格式处理器 / POST 探针,client 需要 glslang 与反射层;`MG_Util` 内部是否有干净的 Transpile-vs-Reflect 缝未审计。P7。 +8. **一份反射归档能否服务三个消费者。** Espryt 那一半已答(P4a):能且不需要复制。Magma 的第一个消费者已由 P5f fm 答:`MagmaProgramSource` 全部从 wire `ProgramArchive` 取,无前端对象、无复制。第二个(storage block 表)今天是**否**且变差了:split 下 `MagmaProgramSource::EnsureStorageBlocks` **每 draw** 重跑 SPIRV-Reflect(monolith 臂 `DirectVulkan.cpp:177-200` 至少有持久缓存);答案是往 `LinkArtifacts` 加有序 `storageBlocks`(数据已在 `blockReflection` 里),两个消费者都改读它。**已答(P7 wave 2-C,`dce9953d..cf7ca59f`)**:`storageBlocks` 入归档、两个消费者改读、每 draw SPIRV-Reflect 4→0;`g_programResourceCaches` 的 monolith 半边归 P13(G1)。 +9. **viewport-array 回放能否塞进一次 `draw_vbo`**:各遍之间观察到的状态是否与今天一致未验证。P8。 +10. **`ResidentSubData` 的不对称。** 已答一半(2026-09-22 核):**Magma 的实现已补**(`VkBufferManager.cpp:123` monolith 表、`:148` wire 表,`fff9d639f`)。**仍未答:`kCapResidentSubData` 从未被 server 发布**——`MG_Backend/Init.cpp:192-219` 是全树唯一的 `SetCapabilityBits` 生产调用点,只 OR 了 XFB、三个 query 位与 run-ahead 位;于是 `PipeFill.cpp:848` 在 split 下恒 false,**两个后端在拆分模式下都退回 `BufferObject.cpp:711-716` 的就地 memcpy**,Magma 的实现在 split 里是死代码。**已答(P7 wave 2-C,ID-P7-20)**:`InitServerRoleCommon` 按 server 自己的 wire 资源表发布 `kCapResidentSubData`,镜像收到即验收;白盒「发出的是 opcode 49」在 P11 采纳档落地前不可达(§5.4 债)。 +11. **`SEG_STAGE` 的上限。** 内容侧已答(2026-09-20):**分块已实现**——buffer 范围走查(`1e7c372e`)与纹理整宽 slab(`9469d48e`)按 `MGPipeStageChunkBytes()`(默认 32 MiB 的 1/4 = 8 MiB)切成多条 `resource_subdata`,所以单次 128 MiB 上传不再需要显式 `MOBILEGL_IPC_STAGE_MB=256`;单片仍超 arena 或该 record 类型未接入分片时仍是 `Fatal{RingOverrun, "SEG_STAGE"}` 兜底。**仍未答(2026-09-22 收窄)**:a6 §6 实测没走查的只剩 **program archive**(唯一无界 blob)与 **`draw_vbo` 的 `NumDraws*12` range 尾**;宿主索引 span 无 producer(划掉),`set_global_constants` 有走查。二者的分片是 P6.5 sl 与 P8 共用的同一件事,stream 发送窗口对着它们定尺。MC in-world / Create 的占用分布仍未量(门 8 已给出两条负载的字节/帧与记录/帧,逐 blob 分布只在 integration 车道取到)。 +12. **P13 之后 split-only 渲染 bug 的 server 侧第二意见。** verify 构建 + recorder 只覆盖推送内容,不覆盖后端对它的解释。 +13. **烘焙后的内部 shader 能否在没有活 `ProgramObject` 的情况下表达 uniform location 与 UBO 布局。** 颜色 blit 已答(P5f fv:`WireColorBlit.{vert,frag}` 用 push constant,`WireColorBlitSpirv.h` 签进树);窄化到深度 mip 程序(今天在 split 下被编译掉、其 split 路径是具名 Fatal)与直通 TCS(`ProgramFactory.cpp:4060-4262`,第三个未盘点的内部 shader 生成器)。**已答(P7 wave 2-B / B2)**:深度 mip 烘焙 (A)(D)(`WireDepthMipmap.*`,push constant,`MOBILEGL_BAKED_INTERNAL_SHADERS` 新鲜度门表驱动)与 (B′)(disaggregated 构建的 monolith 臂改用烘焙模块)落地;multisample resolve 第三套烘焙 shader 随 B2;直通 TCS 仍未盘点。 +14. **推送模型改变哪些按拉取模式调过的缓存命中率。** 幸存者容量在 P13 重调。一条线索:设备上 26.3 Espryt 的 `sve` ≈ draw 数(每 draw 重发一次 sampler-view 集合),桌面只有 ~0.07/draw;列入 P3b/P4b 优化清单。 +15. **monolith 的 `*IndirectCount` 不调 `SyncGpuWrites()` 是不是潜在缺口。** 独立 `dev` 问题,拆分不得借机顺手修。 +16. **索引宿主镜像的实际内存占用。** MC / Sodium / Iris 语料里 element-array buffer 总量未测;若显著超 64 MiB,退化路径的频率与代价必须实测。P8。 +17. **`create-indirect` fixture 在 Adreno 830 上的失败**是 `dev@81b17c0b` 就有的(基线 APK 复现),不是本分支造成;`rd12` + Magma 在两台 Adreno 830 上的 `scudo::reportMapError` 崩溃同样是 `dev` 侧。两者排除在设备 A/B 之外、留在桌面语料里;P8 要在 `create-indirect` 上断言 `roundtrips-per-frame == 0`,所以 `dev` 的修复在别人的关键路径上。 +18. **split 两侧的负载平衡。** P5e 解锁出来的新问题:VD32(~3550 draws/帧)下 apply 线程 **11-12 ms**、client **15.7 ms**,**client 是瓶颈且打满 ~94%**。把工作从 client 挪到 apply 会直接降瓶颈——这在 lockstep 下毫无意义(两侧串行相加),**run-ahead 才使它成为一个杠杆**。供选:编码侧的 emitter、tracker 走查、持久映射的脏页追踪(`change_protection` 在侧写里占 1.3%)。需先重新侧写:等待消失后尾部各项的分母都变了。排在 P5e 之后;无主,2026-09-22 建议挂在路线图推完后的优化阶段,P6.5 的 2×2 矩阵车道顺带重新侧写 +19. **链路带宽预算与压缩。** TCP 终局下 `SEG_STAGE` 字节/帧对的是 Wi-Fi / USB(量级 30–100 MB/s),不是内存;`ARCHITECTURE.md` §14 的 ~1 MB/帧预算在 60 fps 是 60 MB/s,已贴着 Wi-Fi 上限。是否对 stage 载荷做 LZ4 类压缩、纹理上传是否按内容哈希在 server 侧去重,要等门 8 的分布出来再定。P6.5 契约写下第一行之前必答。 +20. **大单次上传的停顿。** 128 MiB 一次上传在 50 MB/s 上是 2.5 s;内容侧分块解决的是"装不装得下",不是"要等多久"。跨机臂上要么接受并记录,要么给上传做异步化(与 P9 的语义一起想)。不假装解决。 +21. **配对 / 认证的形式。** **已裁定(ID-P7-3,2026-09-22):令牌足够,不做 TLS。** 威胁模型是「任何人都能连的远程代码路径」而不是窃听:令牌常量时间比较、≥16 字节、无令牌只 loopback、数据面以 `Welcome.dataNonce` 绑定到已认证的控制连接、fork 前认证。TLS 作为 P12 的开放项保留。 +22. **client 平台范围。** 默认先 Linux + Android(现有 CI 能验),Windows client 第二批(`ShmSegmentWin32` 在;`pipe:` 具名拒绝作废、TCP 替代),macOS 仍不拆分。这决定 `ARCHITECTURE.md` §15.3 "Windows 机器不是正确性门"要不要改。 +23. **半开连接的检测。** Wi-Fi 掉线不产生挂断事件;dl 的闩取自描述符挂断,TCP 上需要 keepalive / 心跳超时并**由传输层报告为挂断**——这与"绝不取自 apply 超时"不矛盾,写清楚免得重吵。P6.5 ct。 +24. **E1 对照的重定义(ID-122)无主。** 建议并入 P6.5 的门设计:S8 的好路径断言(`SessionFaultCount() == 0`)已是它想表达的形状,缺的是一个能被证伪的负控。 diff --git a/docs/Disaggregated/notes/README.md b/docs/Disaggregated/notes/README.md index ad49333c..0f1b3855 100644 --- a/docs/Disaggregated/notes/README.md +++ b/docs/Disaggregated/notes/README.md @@ -1,46 +1,69 @@ -# 工作笔记(原 `~/w7/notes/`) +# 阶段笔记(notes) -这一层是 MGPipe 各阶段的**工作笔记原件**:包计划(`BRIEF-*.md`)、契约草稿、集成者逐条裁定 -(`INTEGRATOR-DECISIONS-*.md`)、只读审计、普查、设备记录、包任务书(`TASK-*.md`)与阶段报告。 +这一层放**各阶段的全部细节**:每个阶段一个目录,目录里的 `README.md` 是该阶段的**汇总**(摘要、阶段表原行、逐门实测、落地形状、当时的进度页),其余文件是那个阶段的原始工作笔记——包计划(`BRIEF-*.md` / `TASK-*.md`)、契约草稿、集成者逐条裁定(`INTEGRATOR-DECISIONS-*.md`)、只读审计与普查、包报告、设备记录与证据。上层四份索引([`../ROADMAP.md`](../ROADMAP.md)、[`../ARCHITECTURE.md`](../ARCHITECTURE.md)、[`../MEASUREMENTS.md`](../MEASUREMENTS.md)、[`../CURRENT_STAGE_PROGRESS.md`](../CURRENT_STAGE_PROGRESS.md))只留摘要并链到这里。 -它们此前只存在于一台机器的 `~/w7/notes/` 下。上层那几份文档(`ROADMAP.md`、 -`CURRENT_STAGE_PROGRESS.md`、`MEASUREMENTS.md` …)**大量引用它们**——裁定编号、审计行号、 -逐门数字的出处几乎全在这里。一份引用了几百次、却只存在于本地未跟踪目录的记录,等于没有记录。 -所以整体移进仓库。 +## 按阶段 -## 路径映射 +| 阶段 | 汇总 | 重要文件 | +|---|---|---| +| P0 卫生、度量、门、骨架 | [`p0/`](p0/README.md) | `BRIEF-P0.md`、`p0-device-findings.md` | +| P0.5 值头与制品头 | [`p05/`](p05/README.md) | `BRIEF-P05.md`、`p05-results/` | +| P1 `PipeInputs` + verify | [`p1/`](p1/README.md) | `BRIEF-P1.md`、`P1-LANE-FINDINGS.md` | +| P2 渲染状态 CSO | [`p2/`](p2/README.md) | `BRIEF-P2.md`、`INTEGRATOR-DECISIONS.md`、`ab/`、`bench/` | +| P3a 句柄 wave 1 | [`p3a/`](p3a/README.md) | `BRIEF-P3A.md`、`INTEGRATOR-DECISIONS.md` | +| P4a 句柄 wave 2 | [`p4a/`](p4a/README.md) | `BRIEF-P4A.md`、`INTEGRATOR-DECISIONS.md`、`ab-redmi/` | +| P5 传输 + inproc | [`p5/`](p5/README.md) | `BRIEF-P5.md`、`PACKAGE-PREAMBLE.md`、`p5-results/` | +| P5b verb 迁移 | [`p5b/`](p5b/README.md) | `BRIEF-P5B.md`、`p5b-results/p5b-close-codex-review.md` | +| P5c 共享内存读点归零 | [`p5c/`](p5c/README.md) | `p5c-audit-v1.md` | +| P5d inproc 性能 | [`p5d/`](p5d/README.md) | `P5D-INPROC-PERFORMANCE.md`(报告)、`RESULTS-P5D-R3.md` | +| P5e run-ahead | [`p5e/`](p5e/README.md) | `P5E-RUNAHEAD.md`(报告)、`INTEGRATOR-DECISIONS-P5E.md`(ID-80..136)、`TASK-*.md` | +| P5f 一切状态上 wire | [`p5f/`](p5f/README.md) | `P5F-WIRE-COMPLETENESS.md`(计划)、`close-report.md`、`magma-runahead.md`、`HANDOFF-CI-INPROC-20260920.md` | +| P6 spawn transport | [`p6/`](p6/README.md) | `P6-SPAWN-PLAN.md`、`P6-CONTRACT-DRAFT.md`、`P6-ENDSTATE-REVIEW.md`、`a6-audit-v1.md`、`gate7-device-ab.md` | +| P6.5 两轴传输 + 跨机 | [`p65/`](p65/README.md) | `evidence-index.md`、`validation-status.md`、`code-review-findings.md` | +| P3b / P4b Espryt 深化 | [`p34b/`](p34b/README.md) | `espryt-d1.md` … `espryt-d3.md` | +| P7 Magma 全量迁移(含 Ph) | [`p7/`](p7/README.md) | `PLAN-PH-P34B-P7.md`、`INTEGRATOR-DECISIONS-P7.md`(ID-P7-1..63)、`HANDOFF-2026-09-23.md`、`device-window-*/`;审查稿在 [`handoff/`](handoff/) | +| **P12**(当前)server 自有屏幕窗口 | [`p12/`](p12/README.md) | `PLAN-P12.md`、`INTEGRATOR-DECISIONS-P12.md`、`device/` | +| P8 / P9 / P10 / P11 / P13(待排) | [`p8/`](p8/README.md)、[`p9/`](p9/README.md)、[`p10/`](p10/README.md)、[`p11/`](p11/README.md)、[`p13/`](p13/README.md) | 目前只有路线图上的完整范围与出口门 | -``` -~/w7/notes//.md → docs/Disaggregated/notes//.md -``` +跨阶段:[`OPEN-QUESTIONS.md`](OPEN-QUESTIONS.md)(开放问题全文,编号稳定);[`handoff/`](handoff/)(P7 的集成者审查稿,路径被 P7 交接文档引用,保持原位);[`recovered/`](recovered/)(从失败的 workflow 运行里捞回的 P0–P2 期材料,`wf1` 设计定稿前的计划草案、`wf2` P0、`wf3` P0.5、`wf4` P1、`wf5` P2;与阶段目录逐字节重复的副本已于 2026-09-24 删除,只留独有的)。 -上层文档里凡写作 `~/w7/notes/...` 的 `.md` 引用,都按这条规则读。 +## 旧引用怎么找 -## 移进来的是什么,没移的是什么 +**2026-09-24 整理时移动的文件**(旧笔记、代码注释与契约里仍按旧名引用它们,文件名没变): + +| 旧位置 | 新位置 | +|---|---| +| `docs/Disaggregated/P5D-INPROC-PERFORMANCE.md` | [`p5d/P5D-INPROC-PERFORMANCE.md`](p5d/P5D-INPROC-PERFORMANCE.md) | +| `docs/Disaggregated/P5E-RUNAHEAD.md` | [`p5e/P5E-RUNAHEAD.md`](p5e/P5E-RUNAHEAD.md) | +| `docs/Disaggregated/P5F-WIRE-COMPLETENESS.md` | [`p5f/P5F-WIRE-COMPLETENESS.md`](p5f/P5F-WIRE-COMPLETENESS.md) | +| `docs/Disaggregated/P6-SPAWN-PLAN.md`、`P6-CONTRACT-DRAFT.md`、`P6-ENDSTATE-REVIEW.md` | [`p6/`](p6/README.md) 同名文件 | +| `docs/Disaggregated/CURRENT_STAGE_STATUS.md` | 并入 [`../CURRENT_STAGE_PROGRESS.md`](../CURRENT_STAGE_PROGRESS.md);其中 P7 的出口门 / 已落地 / 真机三节在 [`p7/README.md`](p7/README.md),P12 部分在 [`p12/README.md`](p12/README.md) | +| 旧 `CURRENT_STAGE_PROGRESS.md` 的 P6.5 第一波与 P6 收官快照 | [`p65/README.md`](p65/README.md)、[`p6/README.md`](p6/README.md) | + +**按编号引用的旧章节**(小节编号在新位置原样保留): + +| 旧引用 | 现在在 | +|---|---| +| `MEASUREMENTS.md` §1 / §2 / §3 / §4 / §5 | [`p0`](p0/README.md) / [`p1`](p1/README.md) / [`p2`](p2/README.md) / [`p3a`](p3a/README.md) / [`p4a`](p4a/README.md) 的"实测"节 | +| `MEASUREMENTS.md` §6 / §7 / §8 / §9 | [`p5`](p5/README.md) / [`p5b`](p5b/README.md) / [`p5c`](p5c/README.md) / [`p5d`](p5d/README.md) | +| `MEASUREMENTS.md` §10 / §11 / §12 / §13 | [`p5e`](p5e/README.md)(§10、§11)/ [`p6`](p6/README.md) / [`p34b`](p34b/README.md) | +| `ARCHITECTURE.md` §17.1–§17.4 / §17.5 / §17.6 / §17.7 | [`p5`](p5/README.md) / [`p5b`](p5b/README.md) / [`p5c`](p5c/README.md) / [`p5e`](p5e/README.md) 的"落地形状"节(上层 §17 留一张索引表) | +| `ROADMAP.md` 阶段表某行、"P5c 计划与审计"、"立即的下一项:P6.5 第一波"、债务表、开放问题 | 对应阶段 `README.md` 的"阶段表行"节;[`p5c`](p5c/README.md);[`p65`](p65/README.md);[`p5b`](p5b/README.md) 末节;[`OPEN-QUESTIONS.md`](OPEN-QUESTIONS.md) | +| `ARCHITECTURE.md:NNN`、`MEASUREMENTS.md:NNN` 这类**行号**引用 | 指向写下它时的那个版本,今天已不对应;按上下文所说的节去找,或 `git show <当时的头>:docs/Disaggregated/<文件>` | +| `~/w7/notes//.md` | `docs/Disaggregated/notes//.md`(这些笔记原先只存在于一台机器的 `~/w7/notes/`) | + +## 收录规则 | | | |---|---| -| **移进来了** | 全部 `.md`,415 份,约 11 MB,目录结构原样保留 | -| **没有移** | 脚本(`.sh` / `.py`)、运行日志(`.log` / `.out` / `.err`)、普查与门的结果数据(`.json` / `.txt` / `.tsv`)、补丁、APK 与 `.idsig`、`nm` 转储 | +| **收录** | 全部 `.md` 笔记,以及阶段自己决定入库的小脚本、判读工装与证据摘要(如 `p7/device-window-*/`、`p12/device/`、`p12/gate/`) | +| **不收录** | 大体积、随每次运行重新产生、只在产生它的机器上有意义的证据数据:完整运行日志、APK 与 `.idsig`、`nm` 转储、设备 logcat。上层文档引用它们时给的是路径,不是内容 | -没移的那些仍在 `~/w7/notes/` 与各构建树下。它们是**证据数据**而不是文档:体积大、随每次运行 -重新产生、且多数只在产生它的那台机器上有意义(绝对路径、设备序列号、构建目录)。上层文档引用 -它们时给的是路径,不是内容。 - -## 两个需要知道的例外 - -1. **P6 的包计划已提升为正式文档**:原 `~/w7/notes/p6/BRIEF-P6.md` 现在是 - [`../P6-SPAWN-PLAN.md`](../P6-SPAWN-PLAN.md),本目录不再保留副本,避免两份同源文档各自漂移。 - 契约草稿同理,见 [`../P6-CONTRACT-DRAFT.md`](../P6-CONTRACT-DRAFT.md)。 -2. **`recovered/`** 是从失败的 workflow 运行里捞回来的材料(`wf1`…`wf5`),与同名阶段目录 - **可能重复**,保留是因为其中几份是当时唯一的完整版本。查阶段记录请先看阶段目录。 +新阶段开工时建 `notes/<阶段>/`,把计划、裁定与报告都放进去;阶段收官时补齐该目录的 `README.md` 汇总,上层索引只加一行摘要。 ## 不受 citation lint 管 -CI 的文档引用 lint 只吃 `docs/Disaggregated/*.md`(非递归,`.github/workflows/test.yml`), -**本目录不在其中,这是有意的**:这些笔记的 `file:line` 是写在各自当时的代码头上的,拿今天的 -HEAD 去解析必然大面积失败,而那不是错误——那是它们作为历史记录的正确状态。要核对某份笔记的 -引用,用它自己声明的那个 revision: +CI 的文档引用 lint 只检查 `docs/Disaggregated/*.md`(非递归,`.github/workflows/test.yml`),**本目录不在其中,这是有意的**:笔记里的 `file:line` 写在各自当时的代码头上,拿今天的 HEAD 解析必然大面积失败——那是它们作为历史记录的正确状态。要核对某份笔记的引用,用它自己声明的那个 revision: ```bash python3 scripts/check_doc_citations.py --rev <当时的头> docs/Disaggregated/notes//.md diff --git a/docs/Disaggregated/notes/p0/README.md b/docs/Disaggregated/notes/p0/README.md new file mode 100644 index 00000000..fe4ff5a7 --- /dev/null +++ b/docs/Disaggregated/notes/p0/README.md @@ -0,0 +1,94 @@ +# P0 — 卫生、度量、门、骨架(2026-09-05) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:边界计数器(`PipeStats`)、`PipeCalls.def` 完整目录 + payload POD + 生成器 G1–G7 + CI `pipe-gates`、`gen_pipe_dirty_surface.py`、`check_doc_citations.py`、`MOBILEGL_PIPE_*` 开关、`MG_Remote/{Protocol,Transport}` 骨架 + `protocol.fbs` + `flatc-check`、retrace `--env` 透传。 +- spike A:Android 应用进程 `fork`+`execve` 第二个原生可执行文件,两台设备都成立(P6 / P12 的交付链前提)。 +- spike B:跨进程外部内存只有 **T0(AHB BLOB 交接)** 在两台设备、两个后端上都是完整读写;Adreno 另有 T1(Vulkan opaque fd)。 +- 基线:真机稳态动态 accessor 每 draw 6.5–9.3 次;Magma 在 26.3 每帧重打包 331 KB 具名 UBO;同 185 次纹理发射 Espryt 整 box 635 KB/帧 vs Magma rect 40 KB("server 选上传形状"的依据)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P0** 卫生、度量、门、骨架 +- **状态**:✅ +- **落地什么 / 范围**:边界计数器;`PipeCalls.def` 完整目录 + payload POD + 生成器 G1–G7 + CI `pipe-gates`;`gen_pipe_dirty_surface.py`;`check_doc_citations.py`;`MOBILEGL_PIPE_*` 开关;`MG_Remote/{Protocol,Transport}` 骨架 + `protocol.fbs` + `flatc-check` + `MG_Test/Wire`;三个严格 no-op 收益;spike A / B;retrace `--env` 透传 +- **验收门 / 证据**:单元 / 集成 / trace 逐名不变;wire 层测试绿;两台设备字节 / 调用基线在案;spike 结论。`MEASUREMENTS.md` §1 + +## 实测:P0(2026-09-05,trace APK `7ef7c7e5`,spike `8a239177`)(原 `MEASUREMENTS.md` §1) + +### 1.1 Spike A — 从应用自身进程 exec 第二个原生可执行文件 + +| 设备 | 结果 | +|---|---| +| Adreno 830 | **OK**:`untrusted_app` 父进程 `fork`+`execve` `/libMobileGLServer.so`,子进程同域同 category、exit 0、`execErrno=0`、零 avc denial | +| Mali | **OK**,同形 | + +主机侧已证:AGP 会把改名成 `lib*.so` 的 `add_executable` 打进 `lib/arm64-v8a/`(`RUNTIME_OUTPUT_DIRECTORY` 重定向到 `CMAKE_LIBRARY_OUTPUT_DIRECTORY`);`posix_spawn` 在 minSdk 26 不可用;应用进程 stdout/stderr 是 `/dev/null`,子进程用 marker 文件证明自己活过。代码:`tools/spikes/server_stub/`、`android-plugin/app/src/trace/cpp/spawn_spike.cpp`、`MOBILEGL_BUILD_SERVER_SPIKE`。 + +### 1.2 Spike B — 跨进程外部内存分档(`tools/spikes/extmem_probe/`,`shell` 域,4 MiB payload,每行一次真 GPU 访问 + 两侧字节校验) + +| 路线 | Adreno 830 | Mali | +|---|---|---| +| T1-opaque-fd(server 导出 `VkDeviceMemory` fd,client `mmap` + 导入) | **OK** 完整往返 | UNSUPPORTED(`VK_ERROR_INVALID_EXTERNAL_HANDLE`) | +| T1-dma-buf | UNSUPPORTED | UNSUPPORTED | +| T1-gles-memobj-fd(`GL_EXT_memory_object_fd`) | **FAIL**:导入接受,但每次 `glMapBufferRange` → `GL_INVALID_OPERATION` | UNSUPPORTED | +| **T0-ahb-blob-transfer**(client 分配 `AHardwareBuffer` BLOB → socket 交接 → server Vulkan + GL 导入) | **OK** 全链 | **OK** 全链 | +| T3-external-memory-host | UNSUPPORTED | PARTIAL:GPU 写对宿主映射不可见(只读档) | + +决定:唯一在两台设备、两个后端上都是完整读写的档是 **T0**;Adreno 另有 T1(Vulkan 路径);Mali 无任何 server 导出路线。Caveat:运行域是 `shell` 不是 `untrusted_app`(`ROADMAP.md` 开放问题 3)。 + +```sh +ANDROID_NDK=$HOME/android-sdk/ndk/27.3.13750724 tools/spikes/extmem_probe/build_android.sh /tmp/extmem-build +adb -s $S push /tmp/extmem-build/extmem_probe /data/local/tmp/ && adb -s $S shell "chmod 755 /data/local/tmp/extmem_probe && /data/local/tmp/extmem_probe" +``` + +### 1.3 边界计数器基线(`MOBILEGL_PIPE_STATS=1`,最后一个完整 120 帧窗口;accessor / memo 数字是软件确定的,两台设备相同) + +| trace(窗口内帧数) | 后端 | draws/f | acc/draw | buf B/f | tex B/f(发射 box / rect) | ubo-global B/f | ubo-named B/f | memo 门(hit/miss) | +|---|---|---|---|---|---|---|---|---| +| `minecraft-1.21.4-in-world`(360) | Espryt | 91.6 | **9.28** | 13.5 K | **635 K**(185 box / 0 rect) | 16.7 K | 0 | ers 9257/2577,etl 10538/1296,eub 10720/1114 | +| 同上 | Magma | 91.6 | **8.56** | 13.5 K | 39.9 K(97 box / 89 rect) | 16.7 K | 0 | mfp 0/10994,mpm 9240/1754,mdt 9120/1874 | +| `minecraft-1.21.4-fabric-iris-bsl-in-world`(120,memo 冷) | Espryt | 23.2 | 21.04 | 32.6 K | 8.8 K | 1.8 K | 0 | ers 1958/1843,etl 722/3079 | +| 同上 | Magma | 23.2 | 11.26 | 313 K | 256 K | 1.8 K | 0(vtxc 1.7 K) | mpm 1890/895,mdt 1573/1212 | +| `improved-transparency-minecraft-26.3`(1200) | Espryt | 1320 | **8.44** | 333 K | 0 | 0 | 0 | ers 156925/2791,etl 148606/11110,eub 148246/11470 | +| 同上 | Magma | 1320 | **6.53** | 173 K | 0 | 0 | **331 K** | mfp 21360/137036,mpm 134421/2615,mdt 156611/1785 | + +门缩写:ers = `EsprytRenderState`,etl = `EsprytTextureSyncList`,eub = `EsprytUnitBindingsEpoch`,mfp = `MagmaDrawFastPath`,mpm = `MagmaPipelineMemo`,mdt = `MagmaDynamicTail`。读法:真机稳态动态 accessor 成本是每 draw 6.5–9.3 次(预测区间 10–25 的下沿),推送要打败的是 ~8 次 accessor + memo 探测;Magma 在 26.3 每帧重打包 **331 KB** 具名 UBO 字节(D-B8);同样 185 次发射,Espryt 整 box 路径 635 K 纹素字节 / 帧、Magma rect 路径 40 K,16×("server 选上传形状"的依据)。 + +```sh +ANDROID_SERIAL= MSYS_NO_PATHCONV=1 python3 tools/trace_replay/run_android_retrace_local.py \ + --case minecraft-1.21.4-in-world --backend DirectGLES --env MOBILEGL_PIPE_STATS=1 --env MOBILEGL_PIPE_STATS_PERIOD=120 +# 数字在结果目录的 mobilegl.log 里,grep 'MGPipe stats:' +``` + +### 1.4 桌面数据点与语料事实 + +- llvmpipe / lavapipe 动态 accessor(`GuiBatchScenario`,memo 冷):Espryt 20.65 / Magma 15.54 次 / draw。 +- dirty-surface 面:`MG_Impl/GLImpl` 41 个文件、926 次 mutator 调用、73 个不同 mutator(`RecordError` 占 836 次);92 次位于同函数内也到达后端的即时发布点,其余由紧随的 verb 发布。 +- 读点覆盖:63 个 `PipeInputs` 字段;后端读点清单 → 调用映射 **0 UNMAPPED**。 +- OOM 探测惯用法:41 个 trace fixture 中 0 例(9 次 `glRenderbufferStorage` 无一在 3 个调用内跟 `glGetError`)→ `glRenderbufferStorage*` 不 ack。 +- `FramebufferSrgb` / `DepthClamp`:六个后端读点消费编译期常量 `false` / 零读点;`glEnable` 落到 `default:` 分支既不存储也不报错;41 个 fixture 无一开启。 +- payload 尺寸(`MGPipeTypes.h` 的 `static_assert`,arm64 与 x86-64 一致):`MGPDrawInfo` 56、`MGHostSpan` 32、`MGPBindRenderState` 12、`MGPResourceDesc` 88、`MGPFramebufferState` 304、`MGPProgramDesc` 192、`MGPSubData` 72;`SEG_CMD` 按 56 B 头定尺,MC 帧 1000–4000 draw 时每帧 56–224 KiB 头字节。 +- persistent map 采纳的既有基线(`dev`,MC 26.3,Adreno):p99 163→21 ms、稳态 40→115 fps、省 ~400 MB;P11 的回归上限对着它。Mali 上传作业数悬崖:~100 个精灵 rect 对一个 union box 是 +6 ms/frame。 + +### 1.5 Harness 事实与陷阱 + +- trace app 从不到达 `MobileGL::DestroyImpl`,`MOBILEGL_PIPE_STATS_FILE` 在设备上永远不会写;只有 `mobilegl.log` 的周期汇总行,`MOBILEGL_PIPE_STATS_PERIOD` 为此而加。 +- `run_android_retrace_local.py` 每棵树共用一个 `.trace-work/android-retrace-result` 根并 `rmtree`,两台设备必须从一棵树**串行**跑。 +- `--env` 值里的 `/data/...` 会被 MSYS 路径转换,用 `MSYS_NO_PATHCONV=1`。 +- ColorOS 首次 `adb install` 卡在 `InstallGuideActivity` 确认页;streamed install 进行中点它会让设备掉线几秒。 +- `minecraft-1.21.1-neoforge-create-indirect-in-world` 在两台设备上都失败,`dev@81b17c0b` 基线 APK 复现——`dev` 侧问题(`ROADMAP.md` 开放问题 17)。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-DOCS.md`](BRIEF-DOCS.md) | Brief: rewrite `docs/Disaggregated/` into a clear, concise design + architecture document set | +| [`BRIEF-P0.md`](BRIEF-P0.md) | P0 implementation brief (MobileGL disaggregation, plan "MGPipe") | +| [`p0-device-findings.md`](p0-device-findings.md) | P0 device runs (2026-09-05 evening, both device locks taken over from the stale 00:39/00:43 holders) | + +从失败的 workflow 捞回的 P0 期材料(与本目录重复的副本已删除,只留独有的): +[`../recovered/wf1/final/`](../recovered/wf1/final/)(设计定稿前的实施计划草案四部分)、 +[`../recovered/wf2/`](../recovered/wf2/)(方案 B / MGPipe 实施计划 v2 的分部与综合稿、`p0-polish-findings.md`、citation lint 自测)。 diff --git a/docs/Disaggregated/notes/p05/README.md b/docs/Disaggregated/notes/p05/README.md new file mode 100644 index 00000000..7f74dd4b --- /dev/null +++ b/docs/Disaggregated/notes/p05/README.md @@ -0,0 +1,24 @@ +# P0.5 — 值头与制品头抽取(`5d99ee43`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:`MG_Pipe/MGPipeValueTypes.h`(不 include `MG_State/GLState`)、`ProgramArtifacts.h`、`Visit()` 归档 + `sizeof` 绊线、CI `-H` include 闭包断言(`scripts/check_include_closure.py`)与 `scripts/symbol_report.py`。 +- 门:测试名零删除、`.text` 不变、符号 0 增 / 0 删 / 42 重命名。没有这一步,server 不链 glslang 的判据不可达。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P0.5** 值头与制品头抽取 +- **状态**:✅ `5d99ee43` +- **落地什么 / 范围**:`MG_Pipe/MGPipeValueTypes.h`;`ProgramArtifacts.h`;`Visit()` 归档 + `sizeof` 绊线;CI `-H` include 闭包断言(`scripts/check_include_closure.py`)+ `scripts/symbol_report.py` +- **验收门 / 证据**:测试名零删除、`.text` 不变、符号 0 增 / 0 删 / 42 重命名 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P05.md`](BRIEF-P05.md) | P0.5 implementation brief — value header + artifacts header + include-closure gate | +| [`p05-results/`](p05-results/) | 6 个文件:三个包(value-types / program-artifacts / include-gate)的实现与评审稿 | + +捞回的独有材料:[`../recovered/wf3/`](../recovered/wf3/)(四份 scout)。 diff --git a/docs/Disaggregated/notes/p1/README.md b/docs/Disaggregated/notes/p1/README.md new file mode 100644 index 00000000..d8e5ae6a --- /dev/null +++ b/docs/Disaggregated/notes/p1/README.md @@ -0,0 +1,57 @@ +# P1 — `PipeInputs` 替换与 verify harness + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:`MG_Backend/MGPipe/PipeInputs.h`(63 字段);277 处 `pGLContext->` 箭头 + 58 行非箭头逐条换成 `MGB_CTX`;逐 verb 类填充点(83 条 `MGP_FILL`)与逐 verb 世代 poison;G4 影子比对器 + verify CI 模式;push-on-mutation 三字段。 +- 门:pull `nm` 不变;单元 1485×3;`integration-gpu` 878;`integration-verify` 818 零 `Fatal{`;79 例 retrace verify 79/79 armed 零分歧。verify 通道找出 9 处缺填充行与 3 个"verb 内后端改前端"字段。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P1** `PipeInputs` 替换与 verify harness +- **状态**:✅ +- **落地什么 / 范围**:`MG_Backend/MGPipe/PipeInputs.h`(63 字段);277 处箭头 + 58 行非箭头逐条转换;逐 verb 类填充点;逐 verb 世代 poison;G4 影子比对器 + verify CI 模式;push-on-mutation 三字段 +- **验收门 / 证据**:pull `nm` 不变;单元 1485×3;`integration-gpu` 878;`integration-verify` 818 零 Fatal;79 retrace verify 79/79 armed 零分歧。§2 + +## 实测:P1(lavapipe / llvmpipe)(原 `MEASUREMENTS.md` §2) + +### 2.1 规模 + +| 量 | 值 | +|---|---| +| 后端 `pGLContext->` 箭头站点 | 277(Espryt 113、Magma 164) | +| 非箭头行 | 58(Espryt 9、Magma 49,其中 43 条是 Magma 的逐 verb `MOBILEGL_ASSERT`) | +| `PipeInputs` 字段 / 不同访问器 | 63 / 62(Espryt 32、Magma 56) | +| 填充点 | 83 条 `MGP_FILL`,覆盖 69 个 verb、9 个类 | +| `SyncPersistentMappedRange` / `SyncGpuWrites` | **21**(Espryt 9 + Magma 12;P5 b1 复核,原记 20 漏了共享 helper `ResolveIndirectCommandBytes`)/ 6 | + +### 2.2 verify 通道发现的两类真问题 + +**缺填充行(9 处)**:`kReadback` 缺 `IsTransformFeedback{Active,Paused}`、`kTextureOp` 与 `kDispatch` 缺 `IsCapabilityEnabled`、`kBlitOrCopy`/`kTextureOp` 缺着色器 blit 用到的 viewport 与顶点 / 缓冲绑定。其中 8 行是静态过近似(代码路径可达但通道未跑到),有意保留——能退役它们的证据只能是动态的(完整 CTS caselist + `MOBILEGL_PIPE_POISON_OMIT`,未做)。 + +**verb 内后端改前端(3 个字段)**:Magma 在 draw 里写前端对象(合成回退纹理、材质化排队清除、覆写 sampler filter),8 条 DirectVulkan 用例与 2 条 trace 报 `Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@Draw*"}`。解法 **push-on-mutation**:前端计数器移动时用 `MGP_NOTE_MUTATION(Field)` 刷新推送块里那一个字段——钩子挂在三个计数器上(`BumpSamplingResolutionGeneration`、`BumpTextureBindGeneration` / `NoteUnitTouched`、高水位分支),不挂在四十个写入点上。 + +### 2.3 验收 + +| 门 | 结果 | +|---|---| +| pull 构建符号与 `.text` | 0 增 / 0 删 / 0 改尺寸 / 0 重命名,`.text` 不变 | +| `MG_Backend` 里的 `pGLContext` | 0 | +| 单元(pull / push / verify) | 1485 × 3 | +| `integration-gpu`(pull) / `integration-verify` | 878 / 818 零 `Fatal{` | +| 79 例 retrace(`MOBILEGL_PIPE_VERIFY=1`) | 79/79,79/79 armed,零 `Fatal{` | +| 两个阴性对照 | 篡改字段变红、抽掉一个填充戳记在那条 verb 上变红 | +| 测试名 | 0 删除,+29 | + +verify 构建的代价:`integration-verify` 与 `integration-gpu` 同量级;79 例 retrace 在 4 路下约 20 分钟。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P1.md`](BRIEF-P1.md) | P1 implementation brief — PipeInputs strangler, per-verb fill points, poison generations, MOBILEGL_PIPE_VERIFY comparator and its CI mode | +| [`P1-LANE-FINDINGS.md`](P1-LANE-FINDINGS.md) | P1 verify-lane findings on the integrated tree (`feat/disaggregated` @ 97b997d5, WSL `~/w7/pipe`) | +| [`p1-results/`](p1-results/) | 19 个文件:各包(core / espryt / magma / verify / fix-*)的实现与评审稿 | + +捞回的独有材料:[`../recovered/wf4/`](../recovered/wf4/)(pull-site 目录分部、BRIEF-P1 第一部分、三份 scout)。 diff --git a/docs/Disaggregated/notes/p10/README.md b/docs/Disaggregated/notes/p10/README.md new file mode 100644 index 00000000..681e3b4f --- /dev/null +++ b/docs/Disaggregated/notes/p10/README.md @@ -0,0 +1,15 @@ +# P10 — sync / query / present 节奏(待排) + +> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。 + +## 摘要 + +- 轮询入口成门铃点 + `MOBILEGL_IPC_POLL_ESCALATE`;DirectGLES 的 fence 完成度改为逐 fence 退休;非 present fence tick;**`PRESENT_CREDIT > 1` 从未量过**(带延迟链路上的首要杠杆);roundtrip 计数器与输入延迟直方图;`SetSwapInterval`(最后一个手写 class-C)。 +- 门:query / XFB / `AsyncCompile` 场景在 split 下绿;draw / state / upload 路径 roundtrip 读零;credit 1..3 在 TCP loopback 与跨机臂上的配对记录。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P10** sync / query / present 节奏 +- **状态**:待排 +- **落地什么 / 范围**:~~client 铸造 query handle~~(已是:`QueryCreate` 为 `kWaitNone`);轮询入口成门铃点 + `MOBILEGL_IPC_POLL_ESCALATE`(0 处;TCP 上是首要杠杆之一);DirectGLES 的 fence 完成度来自逐 fence 退休(`g_completedFrameSerial` 仍只在 `Present` 前进;Magma 那半已由 run-ahead 落地);非 present fence tick(唯一记载处);`present` 1:1(已是);~~credit 默认 1~~(已是默认,`ConfigLoader.cpp:414`);**`PRESENT_CREDIT > 1` 从未量过**(重审 §8.7)——带延迟链路上的首要杠杆,inproc 臂上即可取;roundtrip 计数器与输入延迟直方图(今天只有 `MapPersistentRoundtrips`);`SetSwapInterval`(最后一个手写 class-C,`EmitTables.cpp:1665`) +- **验收门 / 证据**:query / XFB / `AsyncCompile` 场景在 split 下绿;trace 上 draw / state / upload 路径 roundtrip 读零;零 timeout 轮询有界退出;credit 1..3 在 TCP loopback 与跨机臂上的配对记录;配对 A/B 记录 diff --git a/docs/Disaggregated/notes/p11/README.md b/docs/Disaggregated/notes/p11/README.md new file mode 100644 index 00000000..3157eac8 --- /dev/null +++ b/docs/Disaggregated/notes/p11/README.md @@ -0,0 +1,15 @@ +# P11 — persistent map 与 ≥16 MiB 采纳(待排,同机臂专属) + +> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。 + +## 摘要 + +- T0 / T1 只对共享映射成立:`dataPlane = Stream` 上点名 T0 / T1 是具名拒绝并强制 T2。POST 探针选档(T0 主攻,Adreno 可选 T1,T2 回退);`SEG_ADOPT` 生命周期绑 `completedFrameSerial`;`gPipeInputs` 版本化 / 双缓冲。前置开放问题 3(`untrusted_app` 域复核)。 +- 门:`LargeArenaAdoptionScenario` 在所选档下绿;Adreno 830 上 p99 帧时与峰值 RSS 对采纳基线(163 → 21 ms / 40 → 115 fps / ~400 MB)回归不超过 10%。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P11** persistent map 与 ≥16 MiB 采纳 +- **状态**:待排,**同机臂专属、已让位**(2026-09-22) +- **落地什么 / 范围**:T0 / T1 只对共享映射成立:`dataPlane = Stream` 上点名 T0 / T1 是具名拒绝并强制 T2(重审 §6 第 5 项),今天 `MOBILEGL_IPC_ADOPT_TIER` 0/1 已是 Fatal-at-use、`kSegAdopt` 只有枚举;前置开放问题 3。POST 探针档位选择(T0 主攻,Adreno 可选 T1,T2 回退);`SEG_ADOPT` 生命周期绑 `completedFrameSerial`;**`gPipeInputs` 版本化 / 双缓冲**(P5d 研究:draw barrier 延后一个 verb 的前提,须在 P3b/P4b 的句柄键控 twin 表之后)。**P5e 的更正**:P5e 退役 draw lockstep **没有**需要它——ra2 实测证明字段**值**已由 `SuppliedFieldMask` 分区,当时那次竞态在**元数据标量**上,修法是让守卫测事实而非断言未来(ID-135)。本行保留采纳存储的一般版本/时序问题;契约 §3.2 的 per-role stamp 已由 P5f 落地,不再是待办 +- **验收门 / 证据**:`LargeArenaAdoptionScenario` 在所选档下绿;26.3 与两个 Create fixture SSIM ≥ 0.99;Adreno 830 上 p99 帧时与峰值 RSS 对采纳基线(163→21 ms / 40→115 fps / ~400 MB)回归不超过 10% diff --git a/docs/Disaggregated/notes/p12/PLAN-P12.md b/docs/Disaggregated/notes/p12/PLAN-P12.md index 5d0feab6..7932c21b 100644 --- a/docs/Disaggregated/notes/p12/PLAN-P12.md +++ b/docs/Disaggregated/notes/p12/PLAN-P12.md @@ -63,7 +63,7 @@ ## 3. 还没做(P12 未收官) -按 [`../ROADMAP.md`](../ROADMAP.md) P12 行的两个出口门,以及本包任务书列出的改小后的条目: +按 [`../ROADMAP.md`](../../ROADMAP.md) P12 行的两个出口门,以及本包任务书列出的改小后的条目: 1. **出口门 (a) 未达成。** 要求 FCL 同机 spawn、双后端**入世界**、杀 server 产生干净 device-lost latch。 今天验证到的是:trace_replay(**不是 FCL**)经 `--window-surface` + `MOBILEGL_IPC_SURFACE=server` 在屏上重放 OpenRA, diff --git a/docs/Disaggregated/notes/p12/README.md b/docs/Disaggregated/notes/p12/README.md new file mode 100644 index 00000000..a9bbabc2 --- /dev/null +++ b/docs/Disaggregated/notes/p12/README.md @@ -0,0 +1,44 @@ +# P12 — Android 生产窗口路径(「server 自有屏幕窗口」子集已实现,未收官) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 当前阶段。状态摘要在 [`../../CURRENT_STAGE_PROGRESS.md`](../../CURRENT_STAGE_PROGRESS.md);计划与交接 [`PLAN-P12.md`](PLAN-P12.md);任务书 [`BRIEF-onscreen.md`](BRIEF-onscreen.md);裁定 [`INTEGRATOR-DECISIONS-P12.md`](INTEGRATOR-DECISIONS-P12.md)(ID-P12-1..15);只读审计 `map-*.md` 四份。 +- 形状:server APK 自建 `ANativeWindow`(`MobileGLDisplayActivity` 的 SurfaceView,进程内 TCP server,会话串行)把渲染流**上屏**;离屏路径保留、同一时刻一条活跃;client 以 `WindowKind::ServerOwned`(控制修订 3)**完全无头**接入,几何由 server 回传;失窗 → `Fatal{ServerWindowLost}` → 干净 device-lost。 +- 进展:15 个 `(P12)` 提交 + 审查轮修复(10 个问题,ID-P12-5..11)已并入 `feat/disaggregated`;主机门绿(ID-P12-14)、G1 成立(ID-P12-15);真机七项检查在审查轮**之前**的 head 上通过,审查轮之后**未复测**(ID-P12-13)。 +- 未完成:出口门 (a) FCL 同机 spawn + 杀 server 的 device-lost、(b) 跨机 TCP 入世界 + P6.5 必测数;`CONTRACT-P12.md`;阶段表行里的其余条目(`unix:` 监听、DirectGLES 去全局、freezer、多 context、FCL 开关接线、D8 白名单)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P12** Android 生产窗口路径 +- **状态**:**子集已实现、未收官(2026-09-24,自 `9f669e52`,15 提交 + 审查轮 8 提交)**:上屏 server 窗口、`WindowKind::ServerOwned`、无头 client、失窗 device-lost 已落地并过真机七项检查;审查轮 10 个问题已修(两个大项:批内失窗、会话间 Espryt 单元影子悬空;裁定 [`notes/p12/INTEGRATOR-DECISIONS-P12.md`](INTEGRATOR-DECISIONS-P12.md) ID-P12-5–11),主机门复跑,**真机未复测**(ID-P12-13);两个出口门均未打(见 [`notes/p12/PLAN-P12.md`](PLAN-P12.md) §3) +- **落地什么 / 范围**:**按重审 §6 改小**:~~Service 收 Java `Surface`~~ 删掉 Surface 传递那一半——server app 拥有自己的 SurfaceView,client 根本没有窗口,于是 minSdk 26 没有扁平化 `ANativeWindow` 的 NDK API 这条约束不再成立;`WindowKind::ServerOwned`;两个后端各一条 `CreateEGLWindowSurface` 臂;`m_windowHandle` 四处写点 + `sameHandle` 去重键(a6 §6);DirectGLES `g_Display` / `g_Surface` / `g_Context` 去全局;`kEventSurfaceChanged` 的 Width / Height 缺口(Espryt 只发布格式,server 拥有显示时 client 默认 FBO 会停在 512×512 占位值);client 侧不再无条件发 `SetWindowHandle`(`BackendObject_Remote.cpp:215`——今天 FCL 里开 spawn,第一次 `eglCreateWindowSurface` 就在 server 进程 abort);server app 监听 `unix:` 与 `tcp://`(后者 Ph 之后);server 生命周期绑 Activity;cached-app freezer(SPAWN-PLAN §8.2);多 context(契约 §11);FCL env 与 plugin APK 开关表接线 +- **验收门 / 证据**:两个门:(a) Minecraft 经 FCL **同机 spawn** 在 Adreno 830 双后端入世界,杀 server 产生干净 device-lost latch;(b) **另一台机器上的 client 经 TCP** 在 Redmi 上进世界(数据面 stream),并记录 P6.5 的必测数 + +## 状态记录(原 `CURRENT_STAGE_STATUS.md`,2026-09-24) + +**更新:2026-09-24(P12 子集)** · 计划与交接 [notes/p12/PLAN-P12.md](PLAN-P12.md)。**P12「server 自有屏幕窗口」子集已实现、未收官,已并入 `feat/disaggregated`**(15 提交自 `9f669e52` 起,加审查轮 10 个问题的修复——两个大项:批内失窗、会话间 Espryt 单元影子悬空;裁定 [notes/p12/INTEGRATOR-DECISIONS-P12.md](INTEGRATOR-DECISIONS-P12.md) ID-P12-5–15,主机门与 G1 见 ID-P12-14/15,**审查轮之后未上真机复测**,ID-P12-13):Android 上的 server 自建 `ANativeWindow`(自己的 SurfaceView)把渲染流**上屏**,离屏路径保留,同一时刻只一条活跃;client 以新的 `WindowKind::ServerOwned` **完全无头**接入(控制修订 3)。主机门除一条既有环境敏感项外全绿,G1(pull 构建 `.text` `0xa52203`、符号 0/0)已独立复核;真机 Redmi `2f7cbe2e` 上七项编号检查过了双后端上屏、串行/Busy、失窗 device-lost、离屏不回归、无显示具名拒绝、一台设备一个 server。**两个出口门都还没打**:跨机 TCP(门 b)完全未做,FCL + 杀 server 的 device-lost(门 a)未做——本轮的真机是 trace_replay 经 `adb forward` 环路驱动的。上一阶段 **P7 已收官(2026-09-23,ID-P7-63)**:§8 九项全绿——终局窗口 3(`p7w8-78e71f2b`)门 3 PASS 36/36、门 5 PASS;门 4 以 `# P13` 读法关闭。 + +- **P12 子集**:APK `26.09.29b7b28-trace`(**控制协议修订 3**,与 p7w8 的 server 不兼容),证据 `notes/p12/device/`(九格拼图 `evidence-strip.png`)。七个编号检查在三个 head 上跑过,`trial-*/` 是按 head 归档的旧轮次。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-onscreen.md`](BRIEF-onscreen.md) | P12 subset "on-screen server window" — implementation brief (2026-09-23) | +| [`fix-icd.sh`](fix-icd.sh) | 钉 lavapipe ICD | +| [`g1-base-names.sh`](g1-base-names.sh) | G1 基准符号名生成 | +| [`gate-container.sh`](gate-container.sh) | 主机门的容器变体 | +| [`gate.sh`](gate.sh) | 主机门脚本 | +| [`INTEGRATOR-DECISIONS-P12.md`](INTEGRATOR-DECISIONS-P12.md) | P12 集成者裁定日志(「server 自有屏幕窗口」子集) | +| [`map-apk-process.md`](map-apk-process.md) | MobileGL-disagg: Android APK and process model for an on-screen server path (commit 9f669e52) | +| [`map-client-surface.md`](map-client-surface.md) | Client-side surfaces in split mode: map, open questions and change list (commit 9f669e52) | +| [`map-docs-contract.md`](map-docs-contract.md) | Design docs and contracts for a server-owned on-screen window (P12 subset) | +| [`map-server-surface.md`](map-server-surface.md) | Server-side surface path and both backends' window surfaces (commit 9f669e52) | +| [`p203.sh`](p203.sh) | `IterationRPProgram203Scenario` 复核 | +| [`PLAN-P12.md`](PLAN-P12.md) | P12「server 自有屏幕窗口」子集 — 计划与交接(2026-09-24) | +| [`setup-tree.sh`](setup-tree.sh) | 包树搭建 | +| [`android/`](android/) | 27 个文件:真机与构建脚本(`dev.sh`、`check*.sh`、`final-run.sh`、red-once 工具等) | +| [`device/`](device/) | 85 个文件:真机七项检查证据(`evidence-strip.png` 九格拼图;`trial-*/` 为按 head 归档的旧轮次) | +| [`gate/`](gate/) | 6 个文件:三个 head 的构建日志与主机门日志(含审查轮、CMake 4 对照) | diff --git a/docs/Disaggregated/notes/p13/README.md b/docs/Disaggregated/notes/p13/README.md new file mode 100644 index 00000000..f1efd128 --- /dev/null +++ b/docs/Disaggregated/notes/p13/README.md @@ -0,0 +1,15 @@ +# P13 — 退役 pull 路径(待排) + +> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。 + +## 摘要 + +- 删 `SnapshotFromGLContext()` 非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`、`set_residual_value_state`;保留 `MOBILEGL_PIPE_VERIFY`;MGPipe recorder;重调幸存缓存容量;建立模块 target 与边界(D12)。P7 留下的 86 个链接棘轮符号标 `# P13`。 +- 门:`static_assert(sizeof(ResidualValueBlock) == 0)`;三道纯度门在非 verify 构建上转绿;recorder 金标建立;monolith 逐线程 CPU 不差于 P0 基线。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P13** 退役 pull 路径 +- **状态**:待排 +- **落地什么 / 范围**:删 `SnapshotFromGLContext()` 非 verify 分支、`MGB_CTX`、`MOBILEGL_PIPE_PUSH`、`MOBILEGL_PIPE_LEGACY_MEMOS`;保留 `MOBILEGL_PIPE_VERIFY`;MGPipe recorder;删 `set_residual_value_state`;在计数器活着的情况下重调幸存缓存容量;建立模块 target 与边界(D12:`SOURCE_FILES` 是一张平表喂两个库 target,无模块可链) +- **验收门 / 证据**:`static_assert(sizeof(ResidualValueBlock) == 0)`;三道纯度门在非 verify 构建上转绿;recorder 金标建立;monolith 逐线程 CPU 不差于 P0 基线 diff --git a/docs/Disaggregated/notes/p2/README.md b/docs/Disaggregated/notes/p2/README.md new file mode 100644 index 00000000..0c123745 --- /dev/null +++ b/docs/Disaggregated/notes/p2/README.md @@ -0,0 +1,95 @@ +# P2 — 渲染状态 CSO + 第一片 Track H + 残余值块(`738b289d`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:前端 Tracker(dirty 位、5 个聚合世代、集合 hash 抑制器);`MGPipeRenderStateSpans` chunk 表(7 pipeline chunk 396 B + 8 dynamic chunk 772 B)+ G7;`CsoCache`(64);`create/bind_render_state` + `set_dynamic_state`(`SyncRenderState` 一行不动);`ResidualValueBlock` 棘轮 1248 → 8;Espryt slot 表 + Magma vertex input 重键(11 条 memo 删除 + 2 条重键);三个 capability 补真存储。 +- 门:G1 4 处认定 resize、`.text` +160 B;单元 1566×3;`integration-gpu` 916 三臂;retrace 79/79 push + verify。 +- **GO/NO-GO(2026-09-08):继续。** Release 下边界代价 +6–12%(两机两后端一致),用户接受,自此性能只记录、不设门。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P2** 渲染状态 CSO + 第一片 Track H + 残余值块 +- **状态**:✅ `738b289d` +- **落地什么 / 范围**:Tracker(dirty 位、5 个聚合世代、抑制器);dirty-surface 成门;`MGPipeRenderStateSpans` chunk 表 + G7;`CsoCache`(64);`create/bind_render_state` + `set_dynamic_state`(`SyncRenderState` 一行不动);pixel pack / patch / attrib defaults;`ResidualValueBlock` 棘轮 1248 → 8;Espryt slot 表 + Magma vertex input 重键(11 条 memo 删除 + 2 条重键);`MOBILEGL_PIPE_LEGACY_MEMOS`;三个 capability 真存储 +- **验收门 / 证据**:G1 4 处认定 resize、`.text` +160 B;G2 名差 0;单元 1566×3;`integration-gpu` 916 三臂;retrace 79/79 push + verify;对照 44/44。**GO/NO-GO(2026-09-08):继续**。§3 + +## 实测:P2(`738b289d`)(原 `MEASUREMENTS.md` §3) + +### 3.1 门 + +| 门 | 结果 | +|---|---| +| G1 pull 构建符号 | 0 增 / 0 删 / 0 重命名;**4 处认定 resize**(`RenderState::{RenderState, SetCapability, IsCapabilityEnabled}`、`_GLOBAL__sub_I_DirectGLES.cpp`);`.text` +160 B | +| G5 `SyncRenderState` | `RenderStateImpl` 段 sha 逐字节相同 | +| G2 / G14 | 名差 0;0 删除 / +119 | +| 单元 | 1566 × {pull, push, verify} | +| `integration-gpu` | 916/916 × {pull, push, `MOBILEGL_PIPE_PUSH=0`};渲染状态敏感子集 72/72 | +| `integration-verify` | 828 零 `Fatal{` | +| 79 例 retrace | push 79/79;verify 79/79 armed 零分歧 | +| G7 setter 一致性阴性对照 | 变红并点名 `SetColorMask` | +| `CsoContentAddressing` / verify 三组对照 | 6/6 / 44/44(`PoisonOmitted`、`VerifyCorrupted`、`HandleRecycle`) | + +### 3.2 设备配对 A/B(-O0 勘误) + +两机 64 次运行(小米 + Oppo,四 trace × 两后端 × finish 开/关,reboot-clean、钉频、best-of-3、尾 200 帧)所用的两臂 APK 是**未优化构建**(`libMobileGL.so` 43.2 MB / `.text` 21.8 MB,对 Release 15.5 / 9.4 MB),绝对值与差值都是 -O0 读数,不作基准线;Release 基准线以 §4.4 为准。-O0 读数:小米 p50 **+8–14%**(四 trace 两后端),Oppo Espryt **+16–18%** / Magma **+10–11%**,p99 同向;finish 开关两臂几乎一致(多出的是客户端 CPU);计数器两机逐字相同。原始 32 行表在 git 历史(`ea34bccb` 版本的本文件 §10)。harness 误杀记录:`trace-replay-ci.sh` 对 `pidof` 单次采样失败即 force-stop,`dev` 侧跟进。 + +### 3.3 DriverBench:T1 / T2(桌面 llvmpipe / lavapipe,`wsl_p2_bench.sh`,Release,240 帧 × 5 次取中位数,`ns_per_op`) + +| 臂 | `mc_vanilla_draw`(ns/draw) | `mc_state_toggle`(ns/开关对) | `mc_pass_switch`(ns/pass) | +|---|---|---|---| +| native | 4771 | 22968 | 437759 | +| Espryt pull / push / push `PIPE_PUSH=0` / push 位 63 | 5121 / 5443 / 5671 / 5374 | 23753 / 24869 / 24584 / 24630 | 443300 / 446017 / 443807 / 435454 | +| Magma pull / push / push `PIPE_PUSH=0` / push 位 63 | 16900 / 17245 / 17599 / 17444 | 32705 / 33857 / 34082 / 34780 | 438406 / 446067 / 445006 / 444758 | + +| `mc_vanilla_draw`,ns/draw | Espryt | Magma | +|---|---|---| +| **T1** = push − pull(整个边界) | **+322**(+6.3%) | **+345**(+2.0%) | +| **T2** = push(`PIPE_PUSH=0`) − pull(P1 残余填充) | +550 | +699 | +| **T1 − T2**(P2 自己加的减的) | **−228** | **−354** | +| 位 63 − push(CSO 内容寻址净值) | −69(≈ 0) | +200(内容寻址每 draw 省 200) | +| blend-toggle / pass switch | +4.7% / +0.6% | +3.5% / +1.7% | + +读法:P2 的 tracker + CSO 比它替掉的 P1 残余填充便宜;剩下的 T1 是尚未句柄化的填充与 dirty 走查。`mc_state_toggle` 由 `DriverBenchStateToggle` 钉住每帧 46 对开关;位 63 对照的开关 `CsoContentAddressingScenario` 常开(内容寻址臂 `csom ≤ 4` 且 `csom < csob`,位 63 臂 `csom == csob`)。 + +### 3.4 计数器读数 + +`resid=` 已非零且棘轮压到底(1248 → 8):桌面 push retrace `iris-bsl`,`resid=197.07` B/帧 = 每 draw 0.64 块;`csom/csob` = 8 / 1415(CSO 复用比)。小米 push 臂(-O0,计数器与优化无关): + +| trace | Espryt `ers` / `etl` / `eub` | Magma `mfp` / `mpm` / `mdt` | `resid=` B/帧 | `csom` / `csob`(每 120 帧) | +|---|---|---|---|---| +| `minecraft-1.21.4-in-world` | 6020/1850 · 6884/986 · 6962/908 | 0/7278 · 6030/1248 · 5928/1350 | 178.31 | 2 / 1719 | +| `improved-transparency-minecraft-26.3` | 78960/1367 · 75378/4949 · 75199/5128 | 10740/68928 · 67660/1268 · 78800/868 | 185.36 | 0 / 1157 | +| `minecraft-1.21.4-fabric-iris-bsl-in-world` | 113/133 · 85/161 · 82/164 | 0/177 · 86/91 · 81/96 | 370.67 | 1 / 122 | +| `minecraft-1.21.4-startup` | 174/185 · 305/54 · 305/54 | 0/118 · 0/118 · 0/118 | 25.08 | 9 / 181 | + +CSO 内容寻址在稳态窗口里几乎不再铸造;memo 门形状与 §1.3 的拉取基线一致。**未测**:逐 dirty 位触发率(`FireCount`/`WalkCount` 已实现但不在汇总行)、每 draw payload 直方图(只在 teardown JSON 里)。 + +### 3.5 遗留判定 + +- `MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH=1` 的 186 条过滤不进比对器(P3a 在 push 下跑了一次 958/958;进比对器推迟到 P3b)。 +- Track H 单位成本的日历口径未记录(产出侧在案:11 条 memo 删除 + 2 条重键零回归)。 +- 句柄 ABA 对照"重键前红":修复前 `HandleRecycle` 28 条中 1 条红(`AbaControl` 期望死对象的红却看到替换对象的绿);修后 32/32,且关掉 `MOBILEGL_PIPE_HANDLE_ABA_CONTROL` 两臂都 `observed=FRESH` 并失败——污染只由被打掉的身份产生。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P2 出口(2026-09-08):GO/NO-GO 判定继续**——五部分门全绿,逐线程 CPU 代价约 +10% 被接受;tracker 绝对 ns 上限降为记录项。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P2.md`](BRIEF-P2.md) | P2 implementation brief — the frontend state tracker, render state as a CSO + dynamic state, the residual value block, and the first Track H slice | +| [`INTEGRATOR-DECISIONS.md`](INTEGRATOR-DECISIONS.md) | P2 integrator decisions (feat/disaggregated, 2026-09-07) | +| [`scout-docs-spec.md`](scout-docs-spec.md) | P2 specification, extracted from `docs/Disaggregated/` (read-only scout) | +| [`scout-measure-infra.md`](scout-measure-infra.md) | P2 measurement & gating: what exists, what is missing, what it costs | +| [`scout-render-state.md`](scout-render-state.md) | Scout: render state → CSO + dynamic state (P2 groundwork) | +| [`scout-track-h.md`](scout-track-h.md) | Track H, slice 1 — scout report | +| [`scout-tracker.md`](scout-tracker.md) | Scout: what the P2 frontend state tracker must do, and what already exists | +| [`ab/`](ab/) | 4 个文件:小米 / Oppo 配对 A/B 汇总(-O0 勘误见上文 §3.2) | +| [`bench/`](bench/) | 1 个文件:DriverBench T1/T2 | +| [`devices/`](devices/) | 1 个文件:两台设备的定频报告 | +| [`p2-results/`](p2-results/) | 42 个文件:各包(tracker / spans / espryt / magma / gates)的实现与评审稿 | + +捞回的独有材料:[`../recovered/wf5/`](../recovered/wf5/)(BRIEF-P2 的完整版 / 重放版与分部)。 diff --git a/docs/Disaggregated/notes/p34b/README.md b/docs/Disaggregated/notes/p34b/README.md new file mode 100644 index 00000000..7e19d86c --- /dev/null +++ b/docs/Disaggregated/notes/p34b/README.md @@ -0,0 +1,82 @@ +# P3b / P4b — Espryt 深化(P7 的并行流 wave 2-D) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 2026-09-22 审计([`../p7/PLAN-PH-P34B-P7.md`](../p7/PLAN-PH-P34B-P7.md) §1.2)后作为 P7 的并行流推进,与 P7 文件零重叠。 +- 已落地(`origin@22761a69`):**D1** XFB 两洞(孤儿目标保留、回写事件切片)+ 回调 10 → 9 + 四个 resolver 具名启动期 stop + readback 家族三臂;**D2** `TextureViewAliasScenario`、`TextureUploadShape` 由记录升为门(零补丁 red-once)、`espryt_memo_purity.py`、`SubsystemDeps.def`、12 场景 split 普查;**D3** view 在属主上传后陈旧的产品缺陷修复 + D-K2 六处读者读同一张表。 +- 余:R-3 另一半(`MGPSamplerView::Target` 谓词)、`KHR-GL46.texture_view.coherency` 的 split 读数、CTS AFTER;Iris trace 普查已有头上结果([`../p7/iris-census-1135c664.md`](../p7/iris-census-1135c664.md))。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P3b / P4b** 深化(Espryt) +- **状态**:**已审计(2026-09-22,`notes/p7/PLAN-PH-P34B-P7.md` §1.2)**;Espryt 项与 P7 文件零重叠,作为 P7 的并行流(wave 2-D)推进。**D1 / D2 / D3 已落地(`origin@22761a69`,2026-09-22)**:D1 = XFB 两洞(孤儿目标保留 varyings、回写事件切片)+ 回调 10→9 + 四个 resolver 具名启动期 stop + readback 家族三臂(+39/臂,readback 半边其实早已关,ID-P7-24);D2 + 返工 = `TextureViewAliasScenario`、`TextureUploadShape` 成门(散点内缩后零补丁 red-once)、`espryt_memo_purity.py`、`SubsystemDeps.def`(R-5)、12 场景 split 普查(+76/臂)(ID-P7-26/29);D3 = view 在 owner 上传后陈旧的产品缺陷修复(clean gate 读 storage record,三臂 3 红→14/14)+ D-K2 六处读者读同一张表(ID-P7-28/30)。**余**:R-3 另一半(`MGPSamplerView::Target` 谓词)、`KHR-GL46.texture_view.coherency` 的 split 读数、CTS AFTER(wave 4);~~Iris trace 普查结果(wave 3)~~ 已有头上结果(`3c80cd62`,[`notes/p7/iris-census-1135c664.md`](../p7/iris-census-1135c664.md));逐日状态见 [`CURRENT_STAGE_STATUS.md`](../../CURRENT_STAGE_PROGRESS.md) +- **落地什么 / 范围**:view/属主发射游标**别名场景**(代码半已由 P4a `TextureEmit.h` 属主键控游标 + `TextureObjectView::ToOwner*` + P5e tx2 落地,欠验证);`g_fboTextureSyncList`(分离安全——记录臂先返回、退路是具名 Fatal;四个 resolver 改启动期具名 stop,文本删除受 P5e 裁定 1 约束归 P13);memo 重键**已落地**(P4a/P5f),补纯度 grep 门;~~XFB scatter 搬到 client~~(P5c/P5f 以 server staged shadow + `OnBufferWriteback` 落地;剩两处真洞:孤儿捕获目标被静默丢弃、回写事件无界——>128 KiB 即 `Fatal{EventRingOverflow}`);删 fragColor 重推导 workaround 与 `g_broadcastMemo*`(动 pull `.text`,归 P13,只改注释);raw-depth-fetch sampler 的 monolith 清理(可选);**回读收口**(pack 半已关:`set_pixel_pack_state` APPLIER_DERIVED + server 中性读 + client 侧 PACK-PBO 散射;readback 半开:`get-tex-image-shadow` / `copy-image-shadow-mirror` 非 monolith 下仍 abort、split 零覆盖);P4a 的 R-5(D-K2 规则合一处)/ R-11 必须,R-3 / R-4 / R-7 / R-10 可选;~~`ProgramArtifacts.h` 的 NDK 尺寸钉~~(P6.5 portable archive v2 + 布局指纹取代);链接实验分给它的 **14 个符号**(D12,棘轮落地后 Tier 1) +- **验收门 / 证据**:纹理 / program 场景(split 三臂零覆盖,先普查首阻塞再注册);CTS `texture_*` / `shader_image_*` / `packed_pixels` + P4a 欠的 `direct_state_access.framebuffers*` 在 0.5 pp 内(caselist 入库 + monolith 基线 + delta 工具,今天三者皆无);每一个 Iris trace(CI 管道 P6 已建;**`3c80cd62` 主机结果**:39 例 × 双后端 × {monolith, inproc, spawn} 231 次全活,split 与 monolith 逐字节相同 70 / 77 行,7 行分歧均过阈值、无 Fatal;`texture-remint-pull` 未到达,**P9 例外表为空**——[`notes/p7/iris-census-1135c664.md`](../p7/iris-census-1135c664.md) §3–§4);`TextureUploadShapeScenario` 升级成门(Mali 增量无设备,按 Adreno 记录) + +## 实测:P3b/P4b wave 2-D 包 D2(`cf7ca59f`,WSL 桌面 lavapipe/llvmpipe)(原 `MEASUREMENTS.md` §13) + +### 13.1 上传形状金标(R-11 / D-D4,`TextureUploadShapeScenario` 由记录升为门) + +工作负载常量 `kScatteredRects=40`、`kFrames=3`、一条连续带、**三张**纹理(第三张经 +`glTextureView` 的 level 1 上传,使 view/属主重映射落在被计数的窗口内)。 + +| 臂 | emit | box | rect | jobs | client emitter | +|---|---|---|---|---|---| +| `DirectGLES.TextureUploadShape.`(monolith) | 9 | 9 | 0 | 9 | 9 | +| `DirectGLES.Split.`(inproc) | 9 | 9 | 0 | 9 | 9 | +| `DirectGLES.Spawn.` | 9 | 9 | 0 | 9 | 9 | +| `DirectGLES.Tcp.` | 9 | 9 | 0 | 9 | 9 | + +P4a 的记录是两张纹理、无 client emitter 的 `emit=6 box=6 rect=0 jobs=6`(`ctu=0`),与本行 +**不可比**:工作负载不同。 + +`rect=0` 是 **unpack-ring 策略的既定输出**:client 确实建出 30 个 rect(2 texel 间隙, +`RegionsTouch` 不合并;summed area 120 远低于 union box 的 3/4,两道 client 侧谓词都不取 box 臂), +记录把 30 个 region 全部带过去;是 server 在 `Managers.cpp:8649` 的 +`if (UnpackRingAvailable()) dirtyRectCount = 0;` 决定取 box——"One box, one job",Mali 悬崖的既有 +缓解。门钉的就是这个策略的输出。 + +**散点图案本身是被修过的(评审 fable 指出)**:原来的 stride `x=((i*7)%32)*2` / `y=((i*5)%32)*2` +在 64 px 图集里会打到 x=62、y=62,union box 覆盖整个 level,而 server 的 `subRectEligible` +(`Managers.cpp:8486-8491`)要求 `!dirtyRegion.CoversWholeLevel`(`MipmapStorage.h:30-33`,**包围盒** +判据)——两张散点纹理在 ring 策略之前就已经 rect-ineligible,金标数字一样但量的不是同一件事。 +内缩一个 rect(`kScatterMargin`)后 union box 落在 (2,2)..(62,54),决策点才真正带电。 + +红一次因此是**零补丁**的:`MOBILEGL_ESPRYT_DISABLE_UNPACK_RING=1` 一个环境变量就把两张散点纹理 +从 box 翻成 rect——`box=3 rect=6 jobs=183`(3 = 连续带 x3 帧;6 = 两张散点 x3 帧;183 = 3 + 6x30), +`bytes/f[tex]` 同时从 104448 降到 9024,而 26 个像素用例全绿。内缩之前同一个变量是全绿的,这正是 +`CoversWholeLevel` 把决策点短路掉的证据。 + +### 13.2 client 半边的读法(本包的修正) + +`ctu=` 与 `tex[]` 是否在同一个窗口里,按传输分: + +| 臂 | `tex[]` 在哪 | `ctu=` 在哪 | +|---|---|---| +| monolith | 唯一日志 | 同一行 | +| inproc | **server** 日志(两个文件、一个进程、一套计数器) | 同一行 | +| spawn / tcp | **server** 日志 | client 进程自己的计数器,**Present 节奏不同**——实测 `ctu=13` 对 `emit=9`,且用例读取时通常还没写出 | + +这与 §12.7 最后一条"spawn client 的窗口化字段仍不可信(是结构不是缺陷)"是同一件事,从集成用例 +这一侧再次证实。因此断言改读**进程内的 `MGPipeTextureEmitter::SubDataCount()`**(同一个计数器的源 +头,测试进程在四个臂下都是 client),`ctu=` 只在窗口对齐时断言;是否对齐由 +`PeekSplitRuntime()` 回答的传输决定,**不是**由数值是否 >= 0 决定——spawn 下 server 会发布一个 +完全可读的 `ctu=0`,按数值判会把比较悄悄变成 `0 == 9`。 + +### 13.3 未跑项 / 不可调度项 + +- **Mali 帧时增量(D-D4 要求与金标并列)不可调度。** 本阶段只有一台 Redmi(Adreno 830v2), + **无任何 Mali 设备**。按 Adreno 记录形状,偏差在此与场景头部注明:**Adreno-only; Mali delta + deferred**。不建模、不从 2026 年那次测量的条件推算。形状是悬崖的**因**,帧时是它在某一家硬件 + 上的**果**——门守的是因。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`espryt-d1.md`](espryt-d1.md) | espryt D1 — the Espryt (DirectGLES) stream, part 1 | +| [`espryt-d2.md`](espryt-d2.md) | P3b/P4b wave 2-D package D2 — the Espryt stream, part 2 | +| [`espryt-d3.md`](espryt-d3.md) | espryt D3 — the Espryt (DirectGLES) stream, part 3 | +| [`split-scenario-census.md`](split-scenario-census.md) | Texture / program split-scenario census (P3b/P4b wave 2-D, package D2) | diff --git a/docs/Disaggregated/notes/p3a/README.md b/docs/Disaggregated/notes/p3a/README.md new file mode 100644 index 00000000..29eefa61 --- /dev/null +++ b/docs/Disaggregated/notes/p3a/README.md @@ -0,0 +1,117 @@ +# P3a — handle wave 1(Espryt):buffer、VAO(`fde5fda3`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:九条 `resource_*` + 五条 vertex-input 调用接到句柄形 `MGPipeResourceOps`;第七张 Espryt slot 表;`ResourceRespecify` 的 `kNeedsAck` 谓词;VAO twin 六条身份 memo 在句柄臂退役、四条重键;`mpr` 定义;子系统位 7/8,push 默认 `0x1ff`。 +- 门:五部分门全绿,G1 0/0/0/0 `.text +0`;G5 十一函数;单元 1619×3;`integration-gpu` 958 五臂;verify 842;retrace 79/79 ×2。实际 1 天(绊线 27 天)。 +- 记录项:rd12(VAO 切换密)上 P3a 把 Release 帧 p50 差距从 +11% 推到 +27–30%;DriverBench 每 draw 多 Espryt 699 / Magma 422 ns。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P3a** handle wave 1(Espryt):buffer、VAO +- **状态**:✅ `fde5fda3` +- **落地什么 / 范围**:九条 `resource_*` + 五条 vertex-input 调用接线到句柄形 `MGPipeResourceOps`;第七张 Espryt slot 表;`ResourceRespecify` 的 `kNeedsAck` 谓词;VAO twin 六条身份 memo 在句柄臂退役、四条重键;`mpr` 定义;位 7/8,push 默认 `0x1ff` +- **验收门 / 证据**:五部分门全绿:G1 0/0/0/0 `.text +0`;G5 十一函数;单元 1619×3;`integration-gpu` 958 五臂;verify 842;retrace 79/79 ×2。实际 1 天。§4 + +## 实测:P3a(`fde5fda3`,基线 `5cb826b0`,44 个提交,37 文件 / +9273 / −230)(原 `MEASUREMENTS.md` §4) + +### 4.1 五部分门(本地 `~/w7/pipe`) + +| 门 | 结果 | +|---|---| +| 构建 pull / push / verify | rc 0 / 0 / 0(fail-fast,绝不拿陈旧二进制下判决) | +| A 门 include 闭包 / C 门 `pGLContext` / G13 | 4 probes 0 problem / 空 / 空 | +| **G1** | `.text` 10806323 → 10806323(+0);27811 符号 **0 / 0 / 0 / 0** | +| **G5** 十一函数(pool / 延迟释放 / ring / `FlushPendingRangesNow` 与 `FlushPendingRangesFrom`) | 对 `44c2b5cf` 与 `5cb826b0` 都逐字节相同;self-test 两个阴性对照按名变红 | +| `integration-verify` / 79 retrace verify | 842/842 零 `Fatal{` / 79/79 armed 零分歧 | +| G2 / G14 | 差 0 / 0 删除 +58 | +| 单元 | 1619 × 3 | +| `integration-gpu` | 958/958 × {pull, push, `PIPE_PUSH=0`, `0x7f`(G12 子系统关闭臂), `ESPRYT_DISABLE_INVALIDATE_FLUSH=1`} | +| buffer/VAO 族 / `RenderStateSpans`+`Residual`+`VertexInputEmit`+`ResourceEmit` | 202/202 / 48/48 | +| `HandleRecycle`(verify) / 子系统对照 / verify 对照组 | 60/60 / 10/10 / 64/64 | +| G7 vertex-input 阴性对照 | 变红并点名 `IsBgra` | +| 79 retrace push | 79/79 | +| G3b 具名五条 | 桌面全绿(都在 79 例扫描内),但单独的具名扫描因门脚本把正则写成逗号清单而选中 0 例;设备侧按开放问题 17 排除 `create-indirect` → 记为"桌面全绿、设备侧部分" | +| `gen_pipe` / `gen_pipe_dirty_surface`(扫描根扩到 `MG_State/GLState`) | self-test 7 / 21 个阴性对照全部触发;75 个 mutator 全映射,0 COARSE / 0 UNDECIDED | + +### 4.2 "重键前红"与验证轮找到的三个缝隙缺陷 + +buffer 类的红前证据(package C 尚未注册 `MGPipeResourceOps` 时):`HandleRecycle` 的 `.AbaControl` 臂通过(它断言的就是被污染的内容)、`.Legacy` 通过、`.Handles` 可见地 skip;C 注册后两条 skip 转为断言,最终 60/60。 + +| # | 缺陷 | 表现与修法 | +|---|---|---| +| F1 | vertex buffer 条目按属性的 GL 绑定点解析,而不是按属性下标 | 两者只在 `KHR-GL43.vertex_attrib_binding` 题材上分叉;`VertexAttribBindingScenario` 6 → 3 | +| F2 | ensure 路径读 `MGPResourceDesc::HasDefinedContent`(上一次 respecify 时的事实)判断影子有无字节 | `glBufferData(size, NULL)` + `glBufferSubData` 后应用字节被静默丢掉;改为持有前端对象时读 `HasDefinedContent()` | +| F3 | 惰性生成的 twin 从不发布影子基址,fp64 收窄拒绝每个 draw | `hostBytes` 终身 null → Adreno workaround 禁用属性;在算出基址处补发布,只对非采纳资源 | + +方法学:包 worktree 里 `tools/trace_replay/fixtures` 是 LFS 指针,对着指针跑 retrace 报 `passed 2 / 79`、`ssim=None`——**假红**;base-instance 对照在 llvmpipe 上要先关掉原生 `GL_EXT_base_instance` 才可证伪。 + +### 4.3 Track H 单位成本 + +日历:四个包(contract/wire、client、espryt、gates)各一轮实现 + 对抗性评审 + 至多两轮返工,全部在 2026-09-08 一天内,对 27 天绊线 1/27。memo 账:直接删除普查 11 条里的最后一条(`ConvertedVertexStreamKey::sourcePin`);句柄臂上退役 VAO twin 五个同步 memo 成员;重键 VAO twin 索引槽 memo、`ResolvedDrawBuffers`、twin 同步门、`ConvertedFloat64Stream`;**保留**(`MOBILEGL_PIPE_LEGACY_MEMOS` 下仍编译):以上全部成员、`g_pendingFetchBaseInstance` 族、D-K 那组——真删会移走 pull 符号或改 `sizeof`,G1 的 0/0/0/0 就是这条的度量,随 P13 退役。 + +### 4.4 两机 Release 配对 A/B、MC 26.3 的 p99、DriverBench(记录项) + +协议:reboot-clean、`pin_device.sh` 钉频并前后 `check`、两臂背靠背、`--benchmark-repeats 3 --benchmark-tail-frames 200`、best-of-3、`--benchmark-no-finish` 主臂;APK 都是 `3e298c9a` 的 Release trace 构建(`.text` 9.4 MB)。臂:pull = pull 库;P2 = push APK `MOBILEGL_PIPE_PUSH=0x7f`;P3a = push APK 默认 `0x1ff`。`create-indirect` 排除(开放问题 17)。单位 ms/帧。 + +| 设备 | trace | 后端 | pull p50 | P2 p50 | P3a p50 | pull p99 | P3a p99 | 备注 | +|---|---|---|---|---|---|---|---|---| +| 小米 | `improved-transparency-minecraft-26.3` | Espryt | 10.810 | 11.485(+6.2%) | 11.697(**+8.2%**) | 25.457 | 26.322 | | +| 小米 | 同上 | Magma | 10.675 | 11.473(+7.5%) | 11.459(**+7.3%**) | 25.014 | 26.035 | | +| 小米 | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 8.220 | 9.117(+10.9%) | 10.650(**+29.6%**) | 21.895 | 24.487 | | +| 小米 | 同上 | Magma | — | — | — | — | — | 三臂都 SIGABRT(`scudo::reportMapError`,`dev` 侧) | +| 小米 | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt | 1.292 | 1.349(+4.4%) | 1.382(**+7.0%**) | 2.418 | 2.443 | | +| 小米 | 同上 | Magma | 0.485 | 0.504 | 1.010 | 1.469 | 2.187 | 亚毫秒帧,噪声 | +| 小米 | `create-instancing` | Espryt / Magma | 944.4 / 1003.9 | 945.2 / 1019.5 | 949.9 / 1015.6 | — | — | 两帧 fixture | +| Oppo | `improved-transparency-minecraft-26.3` | Espryt | 9.741 | 10.570(+8.5%) | 10.542(**+8.2%**) | 26.312 | 27.022 | | +| Oppo | 同上 | Magma | 8.162 | 8.920(+9.3%) | 8.973(**+9.9%**) | 22.851 | 23.638 | | +| Oppo | `minecraft-1.21.4-rd12-odinlite-in-world` | Espryt | 9.938 | 11.009(+10.8%) | 12.963(**+30.4%**) | 27.137 | 29.765 | | +| Oppo | 同上 | Magma | 8.001 | 8.933(+11.6%) | 10.161(**+27.0%**) | 24.723 | 27.355 | | +| Oppo | `minecraft-1.21.4-fabric-sodium-in-world` | Espryt / Magma | 1.896 / 0.399 | 1.770 / 0.431 | 1.841 / 0.437 | 2.993 / 1.206 | 3.079 / 1.409 | 噪声内 | +| Oppo | `create-instancing` | Espryt / Magma | 1075.1 / 758.3 | 1045.1 / 731.0 | 1060.1 / 760.0 | — | — | 两帧 fixture | + +读法:(1) **P2 边界在 Release 下的真实代价是 +6–12%**,两机两后端一致;(2) P3a 在 26.3 与 sodium 上几乎不再加价,**但在 rd12 上把差距从 +11% 推到 +27–30%**——rd12 每帧 VAO/buffer 绑定切换远多于 26.3,每次切换走一遍 `set_vertex_buffers` 构造 + `ContentHash` + applier 记录 + 逐属性走查(P4a 补的 `csob-blob` 计数器给了字节口径:rd12 1.06 MB/帧,其余用例中位数 2.9 KB/帧;DirectVulkan 恒 0);(3) **MC 26.3 在 Adreno 上的 p99**:25.46 → 26.32 ms(+3.4%),仍在采纳基线的 21–26 ms 档;(4) `mpr` 在设备上成立(26.3 两机都是 8,P2 臂恒 0);(5) 计数器三臂逐字相同。 + +小米 rd12 + Magma 三臂(含 pull)都 `SIGABRT`:`scudo::reportMapError` ← `scudo_calloc`,pull 库与 P3a 前的 Magma 路径符号一致——既有 bug,`dev` 侧任务。 + +DriverBench(`wsl_p3a_bench.sh`,`c20e2f2b`,`mc_vanilla_draw` ns/draw;T2 = `0x7f` 臂): + +| | Espryt | Magma | +|---|---|---| +| **T1** = push(`0x1ff`) − pull | **+1048**(+20.5%) | **+611**(+3.6%) | +| **T2** = push(`0x7f`) − pull | +349 | +189 | +| **T1 − T2 = P3a 自己加的** | **+699** | **+422** | +| 位 63 − push | −6 | +20 | +| blend-toggle / pass switch | +5.2% / ≈ 0 | +2.8% / ≈ 0 | + +读法:P3a 的 buffer/VAO 句柄路径每 draw 多花 Espryt 699 / Magma 422 ns(Magma 没有句柄化的 VAO 消费者,全是 client 侧发射 + applier 记录);进优化清单:vertex-input 发射的 per-draw 抑制与 applier 记录就地更新。`DriverBench` 必须经 `DRIVERBENCH_EGL_LIB` 显式 `dlopen` provider,不得靠 `LD_LIBRARY_PATH`(glvnd 会把 GPU 悄悄换成 llvmpipe)。 + +### 4.5 决定与遗留 + +- **ID-15**:`Managers.cpp` 的三层 flush 排水梯每个预处理臂各一份——push 构建跑 `FlushPendingRangesFrom`,pull 构建跑逐字节不变的 `FlushPendingRangesNow`;两个名字都是 G5 行,各对自己的 pin 比。转发函数不可行(G5 提取器不认预处理)。 +- **M-3**:fp64 顶点数组收窄在句柄臂上少做一处 `SyncGpuWrites`(`SyncFloat64AttributeAsFloat32ByHandle` 手里只有句柄)——默认掩码下两臂唯一的行为差异(fp64 数组 + shader 写过的源 buffer + 无显式回读),P8 把拉取搬到 client 侧即关闭。 +- 整体 diff 终审抓出两个跨包接缝:client 为每个 VAO 铸的 `VertexElementsCso` 槽在 Magma 下泄漏(修为 `~VertexArrayObject` 走后端无关的死亡路径);`FlushPendingRangesFrom` 在 G5 覆盖之外(→ ID-15)。 +- CI 独有的 teardown 崩溃(`exit()` 时静态析构顺序 UAF):`MGPipeSlots()` 等单例与四个持有前端 `SharedPtr` 的静态 `PipeInputs` 改为退出时泄漏(`d54ec57a`、`6515c8e6`、`fde5fda3`),ASan 10/10 干净;复现钥匙 `GLIBC_TUNABLES=glibc.malloc.tcache_count=0`。 +- `dev` 侧跟进(拆分不顺手修):`g_uploadRing` 不被 `OnBackendContextDestroyed` 重置(良性,`RingAvailable` 自愈);`ScopedDefaultUnpackState::s_synced` 没有失效路径。 +- `MG_Test/Buffer/BufferTest.cpp` 的 fixture 只 scope 了 `BufferBackendOps` 不 scope `MGPipeResourceOps`,26 条被路由进 pipe——修后 86/86;给它一个 pipe 形 mock 是跟进项。 +- 峰值 RSS(push vs pull,79 例):`retrace_gate.py` 不报,没有数。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P3a / P4a 出口(2026-09-08)**:各 1 天,远低于 27 / 39 天的再基线绊线,未触发重定基线。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P3A.md`](BRIEF-P3A.md) | P3a implementation brief — handle wave 1 (Espryt): buffers and VAOs behind the `resource_*` / vertex calls | +| [`INTEGRATOR-DECISIONS.md`](INTEGRATOR-DECISIONS.md) | P3a integrator decisions (feat/disaggregated, 2026-09-08) | +| [`scout-docs-spec.md`](scout-docs-spec.md) | 1. The P3a row of the phase table | +| [`scout-espryt-buffers.md`](scout-espryt-buffers.md) | P3a scout — Espryt (MobileGL/MG_Backend/DirectGLES) buffer surface | +| [`scout-espryt-vao.md`](scout-espryt-vao.md) | P3a scout — Espryt's vertex-array surface | +| [`scout-pipe-and-tests.md`](scout-pipe-and-tests.md) | P3a scout — the MGPipe scaffolding and the gates | +| [`ab/`](ab/) | 3 个文件:两机 Release 配对 A/B | +| [`bench/`](bench/) | 1 个文件:DriverBench | +| [`p3a-results/`](p3a-results/) | 19 个文件:各包的实现、评审与终审稿 | diff --git a/docs/Disaggregated/notes/p4a/README.md b/docs/Disaggregated/notes/p4a/README.md new file mode 100644 index 00000000..621f4873 --- /dev/null +++ b/docs/Disaggregated/notes/p4a/README.md @@ -0,0 +1,95 @@ +# P4a — handle wave 2(Espryt):FBO / 纹理 / sampler / program(`8c458cd5`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:十四条族调用接线(framebuffer、sampler state / view、texture params、shader images、shader state、draw / dispatch program、global constants);纹理 / renderbuffer 复用 `resource_*`;六种 kind 按 `{slot, gen}` 重键;`CompositeResolver`;`Named = 3` framebuffer 记录;消费者门 + 客户端依赖表;子系统位 9–12,push 默认 `0x1fff`。 +- 门:G1 0/0/0/0;G5 17 区;单元 1785×3;`integration-gpu` 1117 七臂;verify 920;八族拒绝普查 0。实际 1 天。 +- 方法论:六个包 v1 全过自己的门、六份复审全判 REWORK,最贵的两个缺陷(丢上传、delete 后 UAF)只有**整体 diff 终审**抓得到。 +- 设备换为 Redmi `2f7cbe2e`:P4a 自己在 Espryt 上 +3.4–5.7 pt,Magma 在噪声内。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P4a** handle wave 2(Espryt):FBO / 纹理 / sampler / program +- **状态**:✅ `8c458cd5` +- **落地什么 / 范围**:十四条族调用接线(framebuffer state、sampler state / view、texture params、shader images、shader state、draw / dispatch program、global constants);纹理 / renderbuffer 复用 `resource_*`(目录不加行);六种 kind 按 `{slot, gen}` 重键;`CompositeResolver`;`Named = 3` framebuffer 记录;消费者门 + 客户端依赖表;位 9–12,push 默认 `0x1fff`;emulation 在 split 下具名 Fatal +- **验收门 / 证据**:全门:G1 0/0/0/0;G5 17 区;G2 2902;单元 1785×3;`integration-gpu` 1117 七臂;verify 920;retrace 79/79;八族拒绝普查 0;`PipeApplyPeek` 白盒对照。实际 1 天。§5 + +## 实测:P4a(`8c458cd5`,基线 `37da3c3a`,101 个提交,86 文件 / +30066 / −384)(原 `MEASUREMENTS.md` §5) + +### 5.1 全门(`6035c9d7` 全量 + `8c458cd5` 复跑) + +| 门 | `6035c9d7` | `8c458cd5` | +|---|---|---| +| **G1**(认定 resize 集为空) | 0 / 0 / 0 / 0,`.text` 不变 | 同上 | +| **G5** | P3a 十一函数 rc 0;P4a **17 区 / 3 文件** rc 0,self-test 8 个阴性对照按名变红 | 同上 | +| G2 / G14 | 差 0 / +275 | 差 0(2902 条)/ +314 | +| 单元 | 1772 × 3 | **1785 × 3** | +| `integration-gpu` | 1091/1091 × 七臂 | **1117/1117 × 七臂**(默认 `0x1fff`、`0x1ff`、`0`、`0x9ff`、`0x5ff`、pull、`ESPRYT_DISABLE_INVALIDATE_FLUSH=1`;DirectVulkan 559/559) | +| `integration-verify` | 896/896 零 `Fatal{` | **920/920** | +| retrace(79 例) | verify 79/79 armed 零分歧;push 79/79;G3b 12/12 | push 79/79 | +| 阴性对照脚本 | `g7_negative_control.sh`、`p3a_vertex_input_negative_control.sh`(`IsBgra`)、**`p4a_descriptor_negative_control.sh`(`Layered` 与 `borderColorForm`)** rc 0 | 同上 | +| 子系统对照套件 | `CsoContentAddressing` + `ResourceSubsystemControl` + `ObjectSubsystemControl` 24/24;`0x9ff` 依赖拒绝臂 14/14;`HandleRecycle`(verify)180/180 | 同上 + 184/184 controls | +| 八族拒绝普查 | — | **0**(逐用例读私有日志;`ctest -V` 是假零) | + +G9 的"落地前必须红"没能通过公共 GL 达成(回读模拟自己会设 `GL_DEPTH_STENCIL_TEXTURE_MODE`):改成 `PipeApplyPeek` 白盒断言,变异下 4/4 变红。真正红过再绿的两条来自终审:per-level respecify 丢上传(`0x1fff` 下读回全黑)与 delete-then-draw(`pure virtual method called`)。CTS `direct_state_access.framebuffers*` / `packed_pixels`(G15)未跑,随 P3b/P4b 补。 + +### 5.2 缝的分类(契约七次修正 `c0b`…`c0g` + 一轮缝类审计 + 一轮终审修复) + +| 类 | 实例 | 症状 | 预防 | +|---|---|---|---| +| 编码没定死 | `MGPSubData::Target`、`DepthStencilMode`、`MGPSurface::Kind`、缺 `TextureTarget` | 两侧各自发明一套;`Texture1D == 0` 与 `kMGPipeResourceTargetBuffer == 0` 撞上 | 编码表放进契约 | +| 身份 vs 内容 | 内置 sampler:client 按身份铸、cache 按内容铸 | 查找永远落空 → 17 条 Iris trace 用驱动默认采样器 | 每 kind 两侧 handle 规则表 | +| 记录键错了维度 | framebuffer 记录按"当前绑定"存,DSA 按名字来 | 打进从没收到附件的 FBO | 记录按对象存;`Named = 3` | +| 进程级单例 vs 每上下文命名 | `CompositeResolver` 按管线 GL 名记忆 | 释放另一个上下文还活着的合成体 | 单例的键含上下文身份 | +| 破坏性客户端动作缺前置条件 | 按 acceptance 清 dirty,但 Magma 无消费者、依赖位只在服务端拒 | 上传丢失:66 条 DirectVulkan 用例、`0x7ff` 下 438/491 | 消费者门 + 依赖门都在客户端:一条不发 | +| 快门看不见自己的主体 | `glBindSampler`、SSO 下 `GetCurrentProgram()`、`glBindImageTexture` 只换 level | 记录停在上一次的值(`create-indirect` SSIM 0.887) | "记录字段 → setter → 快门"完备表;优先混入已有世代 | +| 清得太宽 | `resource_respecify` 清掉整张待上传表 | 已接受的那一级永久丢失 | 作用域随调用走 | +| 死亡没通知发射方 | 六个死亡 helper 只释放 slot | 已删句柄解析到已释放对象 → UAF | 死亡转发给每个 emitter | +| 门不能变红 | G7 脚本永远编译不过、`HighWater(ShaderCso)` 取段顶、G9 红前态公共 GL 不可见 | 绿得毫无意义 | 每个门带阴性对照并真跑过一次红 | + +方法论结论:六个包的 v1 全部通过自己的门、六份对抗性复审全部判 REWORK,最贵的两个缺陷(丢上传、delete 后 UAF)是**整体 diff 终审**才抓到的——它们跨包,各方自洽。终审是唯一能看见跨包契约的那一轮。 + +### 5.3 Redmi 三臂 A/B、MC 26.3 的 p99、上传形状(记录项) + +设备换为 Redmi `2f7cbe2e`(与小米同 SoC 同定频点,数值可比;GPU 当时钉 1050 MHz;主动风扇 level 2,40 个样本 40 个 PINNED)。APK `8c458cd5` Release 双臂;`--benchmark-no-finish`,尾 200 帧、best-of-3、逐线程 CPU p50 ms;`0x1ff` = P2+P3a 边界,`0x1fff` = P4a 默认。 + +| 用例 | 后端 | pull | `0x1ff` | `0x1fff` | Δ P2+P3a | Δ 合计 | **P4a 自己** | +|---|---|---|---|---|---|---|---| +| improved-transparency-26.3 | Espryt | 10.716 | 11.754 | 12.124 | +9.7% | +13.1% | **+3.4 pt / +0.37 ms** | +| improved-transparency-26.3 | Magma | 10.603 | 11.543 | 11.585 | +8.9% | +9.3% | +0.4 pt(噪声) | +| rd12-odinlite | Espryt | 8.210 | 10.798 | 11.182 | +31.5% | +36.2% | **+4.7 pt / +0.38 ms** | +| rd12-odinlite | Magma | — | — | — | — | — | 三臂全 `rc=1`(`scudo`,`dev` 侧,换机仍复现) | +| fabric-sodium | Espryt | 1.312 | 1.406 | 1.454 | +7.2% | +10.8% | +3.6 pt / +0.05 ms | +| fabric-sodium | Magma | 0.474 | 0.502 | 0.507 | +5.9% | +7.0% | +1.1 pt(噪声) | +| 1.21.4-in-world | Espryt | 2.369 | 2.732 | 2.867 | +15.3% | +21.0% | **+5.7 pt / +0.14 ms** | +| 1.21.4-in-world | Magma | 1.028 | 1.147 | 1.145 | +11.6% | +11.4% | −0.2 pt(噪声) | +| fabric-iris-bsl | Espryt | 1.727 | 1.740 | 1.811 | +0.8% | +4.9% | +4.1 pt / +0.08 ms | +| fabric-iris-bsl | Magma | 0.742 | 0.788 | 0.786 | +6.2% | +5.9% | −0.3 pt(噪声) | + +读法:P4a 自己在 Espryt 上是 +3.4 – +5.7 pt(0.05–0.38 ms/帧),大头仍是 P2+P3a 的边界;Magma 两臂在四个用例上落在噪声内——消费者门在设备上的读数(Magma 一条 P4a 记录都不发);`vanilla` 是 P4a 占比最高的用例(draw 少、状态切换密)。**MC 26.3 在 Adreno 上的 p99**:pull 25.297 → P4a 26.841 ms(+6.1%),仍在 21–26 ms 档。**上传形状 pull 与 push 逐项相同**(79 例两臂 `tex[emit/box/rect/jobs]`:18451/16060/2391/39926 对 18453/16062/2391/39928,唯一差异是 2 次 `trp` 带来的重放上传)。线索:设备上 26.3 Espryt 的 `sve` ≈ draw 数(9143 / 9138),桌面 ~0.07/draw——每 draw 重发一次 sampler-view 集合,进 P3b/P4b 优化清单。`acc/draw` 在这一波普遍下降而 CPU 上升,不是矛盾而是该计数器的定义。 + +### 5.4 DriverBench(`wsl_p4a_bench.sh`,`8c458cd5`,`mc_vanilla_draw` ns/draw) + +Espryt T1(`0x1fff` − pull)= +1076.1,T2(`0x1ff` − pull)= +1064.7,T1 − T2 = +11.4;Magma T1 = +629.0、T2 = +452.6、T1 − T2 = +176.4。两臂绝对值在两轮之间各漂 ~200 ns 而差只有 10–130 ns,**桌面 bench 分辨不出 P4a 这一档**;可引用的是 T1 ≈ +1.1 µs/draw 的总边界(Espryt)与设备侧三臂表。Magma 的 +176 ns 不是"Magma 在跑 P4a"(消费者门让它一条不发),是 tracker 多算的快门加噪声。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P3a / P4a 出口(2026-09-08)**:各 1 天,远低于 27 / 39 天的再基线绊线,未触发重定基线。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P4A.md`](BRIEF-P4A.md) | P4a implementation brief — handle wave 2 (Espryt): framebuffer, texture, renderbuffer, sampler, program identity and descriptors | +| [`INTEGRATOR-DECISIONS.md`](INTEGRATOR-DECISIONS.md) | P4a integrator decisions (feat/disaggregated, 2026-09-08) | +| [`scout-docs-spec.md`](scout-docs-spec.md) | P4a docs specification — everything `docs/Disaggregated/` says about handle wave 2 | +| [`scout-espryt-framebuffer.md`](scout-espryt-framebuffer.md) | Scout — Espryt's framebuffer / renderbuffer / texture surface that P4a converts | +| [`scout-espryt-sampler-program.md`](scout-espryt-sampler-program.md) | P4a scout — Espryt's sampler / image / program surface | +| [`scout-frontend-state.md`](scout-frontend-state.md) | Scout — the frontend (MG_State/GLState + MG_Impl/GLImpl) as P4a's client emission must read it | +| [`scout-pipe-and-client.md`](scout-pipe-and-client.md) | P4a scout — the MGPipe scaffolding and the P3a client conventions | +| [`ab/`](ab/) | 5 个文件:小米三臂 A/B(含前缀 `a38bdab4` 的一轮) | +| [`ab-redmi/`](ab-redmi/) | 2 个文件:Redmi 两臂 / 三臂 A/B | +| [`bench/`](bench/) | 1 个文件:DriverBench | +| [`p4a-results/`](p4a-results/) | 41 个文件:各包的实现、评审与终审修复稿(含 `finalfix-v1-logs/`) | diff --git a/docs/Disaggregated/notes/p5/README.md b/docs/Disaggregated/notes/p5/README.md new file mode 100644 index 00000000..e7b59c77 --- /dev/null +++ b/docs/Disaggregated/notes/p5/README.md @@ -0,0 +1,135 @@ +# P5 — 传输 + inproc applier + 发射表(`ff2994d9..37fc4fdb`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:同一 codec 上的 `InProcessTransport`;四种 build flavour(pull / push / verify / split);发射表三类 + caps mirror + reply mailbox;apply 线程终身持有 context;lockstep verb barrier;tight ReadPixels;persistent-map 块推送;G8 字段归属生成器;split 测试 / CI / APK 车道。八包 c0 / w1 / s1 / p1 / b1 / t1 / c1 / v1 + j0 / x2 / v1-r3 收尾。 +- 门:joint OpenRA inproc 2/2 SSIM 1.0、`integration-split` 21/21、E1 14/14 红、G1 0/0/0/0。收尾头全门在 E3(a) 停止(设计性 skip 被判红),Part 2 / 4 未到达。 +- 收官审查 1 blocker / 10 major / 2 minor → P5b 的 r1 / r2。Redmi 四臂 A/B 的 split 臂全部停在索引 draw,barrier tax 归 P5b。 +- 流程在 P5 尾声改变(ID-66):不过度验证,每阶段收官一次异模型族审查。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5** 传输 + inproc applier + 发射表 +- **状态**:✅ `ff2994d9..37fc4fdb` +- **落地什么 / 范围**:同一 codec 上的 `InProcessTransport`;四种 build flavour;发射表三类 / caps mirror / reply mailbox;apply-thread context 终身持有;lockstep verb barrier;tight ReadPixels;persistent-map 块推送;G8 字段归属生成器;split 测试 / CI / APK 车道;八包(c0 契约、w1 codec、s1 session、p1 归属、b1 persistent map、t1 车道、c1 路由、v1 server)+ j0 / x2 / v1-r3 收尾 +- **验收门 / 证据**:joint:OpenRA inproc 2/2 SSIM 1.0;`integration-split` 21/21;E1 14/14 红;G1 0/0/0/0。收尾头全门在 E3(a) 停止(设计性 skip 被判红),Part 2/4 未到达;收官审查 1 blocker / 10 major / 2 minor → P5b r1/r2。§6 + +## 实测:P5(joint `e61d0012` → 落地 `eec0e836` → 收尾头 `37fc4fdb`)(原 `MEASUREMENTS.md` §6) + +### 6.1 五部分门(joint)与落地快门 + +| 部分 | joint 实测 | +|---|---| +| 1 接口纯度 | include closure 4 probes / 0 problems;pull `MG_Remote` 符号 0、split 610;G1 `.text` 10806611 → 10806611、27814 符号 0/0/0/0;P3a/P4a G5 对 `ff2994d9` byte-identical | +| 5 覆盖 / 生成器 | `gen_pipe` self-test 9/9;dirty-surface 27/27(`UseProgram` 一项明确 UNDECIDED);field ownership 15/15;emitter/CSO 185/185;verify controls 4/4 | +| 3 行为 A/B | G2 差 0;G14 0 / +42;unit 1816×3、split 2038;Wire 58/58;`integration-gpu` pull 1128、push 1128、split-monolith 1149;split-inproc 普查 426 pass / 185 skip / 511 abort / 27 fail;`integration-split` 21/21(19 run、2 design-skip);persistent arm 2/2,split `pmap=2160.00, mpr=1`,push `pmap=0.00, mpr=1` | +| 2 语义 / retrace | `integration-verify` 930/930 零 `Fatal{`;OpenRA inproc 2/2 双后端 SSIM 1.0;push retrace 79/79;verify retrace 79/79 armed | +| 4 设备 | 未跑(joint 禁止 adb);见 §6.4 | + +落地快门(`eec0e836`,ID-66):split unit 2038;`integration-split` 22/22;push `integration-gpu` 1128;G1 0/0/0/0 `.text +0`。 + +### 6.2 退出门 E1–E6 与 27 个 wrong-answer + +| 门 | 实测与"为什么会红" | +|---|---| +| E1 barrier | reduced path 19 pass / 2 skip / 0 fail;`MOBILEGL_IPC_VERB_BARRIER=0` 选中的 14/14 全 abort,每个私有日志有自己的 `Fatal{BarrierViolation, ""}` | +| E2 OpenRA | 2/2 SSIM 1.0;x2:丢 29 clears 仍 SSIM 1.0(被后续地形 draw 覆盖),改为丢 758 `DrawVbo` 后 SSIM 0.000036;r2 验证真实 CTest 顺序 baseline → pull-library control → 恢复原库 → draw-drop,并断言库身份 | +| E3 persistent map | (a) 收尾头 6 selected 中 4 pixel red、2 条 `TheMapLandsInTheArmItsLaneDeclares` 设计性 skip 被 j0 判红 → r2 精选默认 / SmallRing 下 4 条 pixel case 各带自己的 assertion + 私有诊断;(b) ID-42 emulated membership green → 3 red → green;(e) x2 证明 wrap,r2 证明真实 retirement wait(1 MiB 臂 `ringwraps=1 ringwaits=1`,8 MiB 对照同时红) | +| E4 field ownership | 生成器 15/15;strict lane 19 abort / 2 skip,首条 `Fatal{UnmigratedPipeInput, "GetTextureContextId@Clear"}`——BARRIER-PULLED 债务的响亮读法 | +| E5 honest inproc | 19 pass / 2 skip;v1-r3 的 `StagedShadowProductionTest` 证明 ensure 上传 server shadow(删保护会读 client A 变红),覆盖 unmapped 对象;coherent-map 的 server 侧绕过由 r1 #1 关闭 | +| E6 phase gate | 普查在 v1-r3 修掉六条过宽 whole-store 拒绝后 **432 / 185 / 505 UnmigratedVerb abort / 27 failed / 0 segfault**(1149 项,`~/w7/p5b-c0b-census-logs/results.json`) | + +| 27 个 wrong-answer 家族 | 条 | 去向 | +|---|---:|---| +| `LayeredAttachmentShapeScenario` | 14 | P4b/P7 layered texture readback | +| packed depth/stencil `GetTexImage` | 3 | P4b/P7 texture-shadow readback | +| framebuffer `HandleRecycleScenario` | 5 | P4b/P7 readback(不是已证明的 handle recycle bug) | +| `PrimitivesGeneratedNoXfbScenario` | 3 | P7 query | +| `TextureParamsWithoutASamplerView` | 1 | P6 inspection forwarder | +| `P4aFinalFixScenario` FBO/RBO delete | 1 | 读回用 ReadPixels,完整像素因果未隔离;client-thread framebuffer death 归 r1 #2 | + +> 上表是 P5 的历史去向。**头上读数(`3c80cd62`,P7 wave 3 同名重跑)见 §7.2 末**:22 条 readback → 19 绿 + 3 条由错答变为具名停止(都是 `MOBILEGL_PIPE_PUSH=0` 的对照臂);3 条 query 全绿;inspection 与 FBO/RBO 各 1 条绿。 + +### 6.3 R-10、逐帧 ledger 与内存 + +规范 ledger(`ProtocolSmokeTest` 钉住):**SEG_CMD 8 MiB / SEG_STAGE 32 MiB / SEG_REPLY 16 MiB / SEG_EVENT 256 KiB**。`MOBILEGL_PIPE_STATS_PERIOD=1` 下 persistent-map 场景 `pmap=600.00 B/帧, mpr=1, rsp=35`(`rsp` 只是有 stamp 读点的下界)。inproc 进程峰值 RSS:server accept 时 11.5 MB、client teardown 141 MB(两角色共享一个进程,不是两个独立峰值)。 + +max record bytes(x2,`37fc4fdb`):Triangle / persistent map / OpenRA 25 帧 / SmallRing 都是 **`maxrec=784 B` / `SetVertexAttribDefaults`**,默认 cap 4 MiB(占 0.019%)——这些负载不需要 chunking;oversized `DrawVbo` 尾的 unit 具名拒绝 `Fatal{RingOverrun, "DrawVbo"}`。 + +**内容分块落地(2026-09-20,`1e7c372e` buffer / `9469d48e` texture)**:`SEG_STAGE` 仍是"一条记录一个整 blob"的 arena,但会超出它的内容由发射侧按 stage chunk 预算 `MGPipeStageChunkBytes()` = `clamp(segment/4, 4096, segment)`(默认 32 MiB → 8 MiB)切成多条 `resource_subdata`——buffer 范围走查与纹理一级的整宽 slab(服务端 `StagedTextureStore::AdoptRun` 拼回整级)。§7.2 普查为容纳单次 128 MiB 上传而显式设的 `MOBILEGL_IPC_STAGE_MB=256` 因此不再是这两条路径的必需;未接入分片的 record 类型仍由 `Fatal{RingOverrun, "SEG_STAGE"}` 具名拒绝。上段的 `maxrec` 数字是分块前的测量,当时分块后的逐 blob 字节分布尚无新测量;该分布与分块后的 `maxrec` 已在 §12.4 补测——结论是默认 8 MiB chunk 预算在已测的两条负载上从未被打到,`maxrec` 仍是 1808。 + +### 6.4 Redmi 四臂 A/B(split 未测) + +会话 2026-09-16,runner `eec0e836`,三份 APK 源码头 joint `e61d0012`;臂 = pull APK / push APK / split APK + `MOBILEGL_TRANSPORT=inproc` / splitctl(split APK 不设 transport)。40 组前后 pin check 全 P/P,37.2–39.9 °C。**27 组完成 / 13 组失败:全部 10 组 split 在 benchmark 前中止**(improved-transparency 双后端 `DrawElementsInstancedBaseVertex`,其余 `DrawElements`),另 3 组是 rd12/DirectVulkan 的既有 `scudo` 崩溃。帧 p50 / p99(ms): + +| case / backend | pull | push | splitctl | +|---|---:|---:|---:| +| improved-transparency / DirectGLES | 10.535 / 25.415 | 12.106 / 26.973 | 12.235 / 27.131 | +| improved-transparency / DirectVulkan | 10.742 / 25.031 | 11.678 / 26.134 | 11.837 / 26.476 | +| fabric-sodium / DirectGLES | 8.314 / 9.573 | 8.311 / 9.701 | 8.303 / 9.486 | +| vanilla 1.21.4 / DirectVulkan | 1.043 / 2.293 | 1.159 / 2.404 | 1.179 / 2.426 | +| rd12 / DirectGLES | 7.999 / 22.089 | 11.105 / 24.865 | 11.261 / 24.771 | + +splitctl 相对 push 的帧 p50 −0.1% – +1.6%(split build 的 monolith 臂与 push 同源等价)。barrier tax 在 P5 未测得,归 P5b(§7.4)。 + +### 6.5 收尾头停止点、r1 / r2 + +`~/w7/p5-final-gate.log`(`37fc4fdb`):构建 4 × rc 0;Part 1 G1 27,814 符号 0/0/0/0、`.text +0`、两条 G5 byte-identical;Part 5 完成;Part 3 unit 1817×3 / split 2086、Wire 58、pull/push GPU 1128、split-monolith 1149 零失败、reduced 22 零失败、broad lane 1149 selected(532 non-success,含 abort);E1 14 red;**E3(a) 因 2 条设计性 skip 被 j0-v3 的逐选项拒绝判失败,Part 2 / 4 未到达**。j0 的 skip 拒绝是对的(不能放宽),选择器才是错的。 + +收官审查(`p5-close-codex-review.md`,只读):1 blocker / 10 major / 2 minor(ID-73)。r1(核心四项):apply-role 不得进入 persistent-map producer;framebuffer death 转 apply mailbox;`PACK_SWAP_BYTES` 的 component / packed-word swap;command-ring retirement 等待与重试。r2(`37fc4fdb..d50183cb`,CI / 控制 / 跑器九项):broad debt lane 记录后继续硬门、全 skip 则 baseline 失败;E2 库身份 SHA256 恢复;E3(a) 精选 4 pixel case;`ringwaits` 只计真正阻塞的分配;split unit 强制 `MOBILEGL_ITEST_REQUIRE_GPU=1`;普查跑器零执行拒绝、launch 前清私有日志;G5 pin 自测驱动生产选择路径;c1f 改用 ID-67 用例。r2 包门:split unit 2087、reduced 22、push 1128 零失败;E1 14 own reds、E3(a) 4 own reds;smoke 16 core + 12 private;census 432/185/505/27。r2 没有新跑 pull G1(不影响 pull production code)。 + +过程记录(不计入实现产量):wave-1 跨族复审十项全部经独立 perturbation 确认(ring 空 wrap 算术、reply 几何差 16 B、blob 校验接受任意段、pad-bit 控制没发送该 bit、三个 CI 对照接受任意非零退出……);后续 v1 / c1 各轮的 review 计数在 ID-52/58/62/64;本地 `rereview` 身份污染的 81 + 58 个提交经 parent/env rewrite 接回 GitHub `ff2994d9`(ID-40),此后每阶段首个 commit 前先跑 `git var GIT_COMMITTER_IDENT`。这些数字是流程在 P5 尾声改变(ID-66)的原因。 + +## 落地形状(原 `ARCHITECTURE.md` §17.1–§17.4) + +### 17.1 verb barrier 与诚实的同地址空间传输 + +P5 的 `inproc` 是真第二线程,但还是 **lockstep**:每条 class-B verb 发射后 client 的 `EmitAndWait` 等到 `appliedSeq == emitSeq`;`MOBILEGL_IPC_VERB_BARRIER=1` 默认开启。理由不是吞吐,而是 BARRIER-PULLED 字段(G8 表里目前 36 行)尚无记录载体;在这些字段退役前让两线程同时跑,server 会读到 client 的"未来值"(R-1)。barrier 是逐族可退役对象,不是 P6 transport 的要求;它的设备代价(barrier tax)见 `MEASUREMENTS.md` §7.4。 + +同一地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写成 `nullptr`,内容 blob 必须带真实 `SEG_STAGE` offset / 非零 size;decoder 对四种形状分别 `Fatal{ProtocolCorruption}`。`MapPersistent` 在 split 恒 decline;`MOBILEGL_IPC_AUDIT=1` 在 retire 后把 staging 填 `0xDD`,让跨 applier 返回持针的实现下一次读取时可见地失败(R-11:任何 widened read 先过 `RequireStagedCoverageForPendingRanges`,否则 `Fatal{StageSnapshotTooNarrow}`)。apply 角色不得进入 client 的 persistent-map producer(r1)。 + +reply mailbox 以**记录序号作为 slot id**(R-3):slot header 是 `{Seq, Status, Size}`,acceptance 的 Bool 答案与 `MapPersistent` decline 都在既有 verb wait 内读取;`Status=ERROR` 一律 `Fatal{ReplyError}`,未知 status 是 `Fatal{ReplyStatusInvalid}`;读到 reply 之前先由 `appliedSeq` 证明该记录已离开 applier(R-5)。P9 才把这套同步 mailbox 推广成异步池。等待预算:普通 verb / 容量等待 120 s(`Fatal{BarrierTimeout}`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`);`ClientWaitSync` 的 applied/reply 预算 = ceil(timeout ns / 1e6) + 120 000 ms,有限 chunk 避开溢出。这条看门狗只判**死锁**(记录永不退役),不是性能门:lavapipe/llvmpipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,故预算从原 30 s 提到 120 s——monolith 走同一路径本就没有这个上限。 + +### 17.2 71 槽的三类、caps 与 tight readback + +client 表(`Client/EmitTables.cpp`)把后端函数表的 71 个槽分成三类,`static_assert` 钉住 **A = 2**(getter 从 caps mirror 本地回答)/ **B = 54**(发射:P5 的 `Clear`、`DrawArrays`、`ReadPixels`、`Blit`、`Present` + P5b 的 d1 19、i1 7、t2 6、f1 11、`BlitNamedFramebuffer`、sync 5)/ **C = 15**(`Fatal{UnmigratedVerb}` 具名拒绝,永不回落到 monolith applier——R-4)。applier 侧 33 条走生成路由表 + 4 条 escape,`PipeCatalogueTest` 钉 37;生成路由的完备性覆盖值调用与 `&MGPipeApply*` 地址取用(R-17)。 + +能力存活只读 `CapsMirror` 中的 `MGPCaps::CallMask`(R-8);server 的 consumer mask 由 backend 类型显式设置并以实际 op table 校验,**永不**从进程级 `MGPipeGetResourceOps()` 推导。成功的 `MakeEGLCurrent` / `InitCapabilities` 重新发布 snapshot,client 按 generation 采纳(R-12);相同 (dpy, draw, read, ctx) tuple 的重复 make-current 不重发(ID-67)。 + +`ReadPixels` 在线上恒为 **tight**:server 临时设 neutral pack,只向 reply 写 `width*height*bytesPerPixel`;client 用自己持有的 pack state scatter(含 `PACK_SWAP_BYTES` 的 component / packed-word swap,r1)。绑定 PACK PBO 时在 client 侧 `Fatal{UnmigratedVerb, "ReadPixels+PACK_BUFFER"}`;reply 单槽 payload 上限 `2 MiB - 16`,更大 readback 的 carrier 留给 P6+(ID-47/57)。 + +### 17.3 server 角色、shadow 与退出 + +`ServerLoop` 的 `mgl-srv-apply` 是 native context 的终身 owner。`ServerMakeEGLCurrent` 对 tuple **只 bind 一次**,client release 只记账、绝不让 apply thread native-unbind(ID-54)。罕见 EGL 操作经 caller-serialised one-slot control mailbox 进 apply thread;十二个 `Server*` forwarder 是唯一缝。有 backend 却无 thread 时 `Fatal{ApplyThreadNotRunning}`,不允许回落到 app thread。framebuffer death 的驱动调用也经 mailbox 上 apply thread(r1)。 + +`PipeApplier::ApplyOne` 依次 stamp verb、decode/apply、清 stamp;只有 `SessionConsumer::ApplyOne` 每条记录把 `appliedSeq` 加一。`StagedShadowStore` 按 resource twin 复制并合并**精确覆盖范围**;`Ops_H_SubData` / flush / respecify 只把 server-owned copy 交给后端;有传输时 `liveHostBase()` 不得回落到 client 的 `MappedData()`(ID-50/52)。 + +退出顺序:`ShutdownSplitRoles` 先让 client publish 并 bounded-drain,再 shutdown transport / 唤醒 wait,随后 bounded-join apply thread;线程在仍持 context 时 detach decoder、销毁 private backend,最后才销毁 emitter 与 transport。 + +### 17.4 lane 隔离与阶段边界 + +每条 `DirectGLES.Split.*` entry 有独立 `MOBILEGL_LOG_FILE_PATH`;E1/E3 控制从被选 entry 的私有文件取 Fatal,且把"选中条目被跳过"判为失败(ID-53/62)。`integration-split` 是缩减路径的硬门;broad inproc `integration-gpu` 车道只记录普查、不设门(ID-65)。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P5 出口(2026-09-16)**:缩减路径首个 IPC 帧;最终全门在 E3(a) 停止;红米四臂的 split 均停在索引 draw,barrier tax 未测(归 P5b)。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P5.md`](BRIEF-P5.md) | BRIEF-P5 — 传输 + inproc applier + 发射表 | +| [`PACKAGE-PREAMBLE.md`](PACKAGE-PREAMBLE.md) | P5 包通用前言 — 每个包开工前必读 | +| [`scout-backend-install-and-thread.md`](scout-backend-install-and-thread.md) | P5 scout — backend installation, `BackendObject_Remote`, and the thread/EGL question | +| [`scout-caps-reply-reverse.md`](scout-caps-reply-reverse.md) | P5 scout — the caps snapshot, the reply slot, and the reverse channel | +| [`scout-gpu-writes-and-persistent-map-push.md`](scout-gpu-writes-and-persistent-map-push.md) | P5 scout — GPU-written tracking and the persistent-map push | +| [`scout-premortem.md`](scout-premortem.md) | P5 scout — adversarial pre-mortem | +| [`scout-test-ci-plumbing.md`](scout-test-ci-plumbing.md) | P5 scout — test/CI plumbing a SPLIT arm needs | +| [`scout-transport.md`](scout-transport.md) | P5 scout — MG_Remote/Transport as it stands, and what P5 must add | +| [`scout-unmigrated-census.md`](scout-unmigrated-census.md) | P5 scout — the unmigrated-input census | +| [`scout-wire-codec.md`](scout-wire-codec.md) | P5 scout — the wire codec (G3) and the call catalogue's flags | +| [`verb-census.md`](verb-census.md) | verb-census.md — which `GlobalBackendFunctionsTable` slots the reduced path actually reaches | +| [`p5-results/`](p5-results/) | 48 个文件:各包实现 / 评审 / joint / A/B 结果稿 | +| [`prompts/`](prompts/) | 2 个文件:派包提示词 | diff --git a/docs/Disaggregated/notes/p5b/README.md b/docs/Disaggregated/notes/p5b/README.md new file mode 100644 index 00000000..439a51cf --- /dev/null +++ b/docs/Disaggregated/notes/p5b/README.md @@ -0,0 +1,150 @@ +# P5b — `inproc` verb migration(`37fc4fdb..82683d4a`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 落地:以 class-C 动态普查决定顺序(Minecraft 优先);d1 19 / i1 7 / t2 6 / f1 11 槽迁移 + sync 五槽 + 具名 blit + GLES mip descriptor;r1 / r2 收掉 P5 收官审查;发射表 A=2 / B=54 / C=15。 +- 门:主机全门 `348d22a4` complete;`integration-split` 107/107;合并 inproc 普查零回退;79 trace 72 / 6 / 1。 +- **Redmi 出口**(`82683d4a`,APK `p5bcodex2`):正确性 8/8——四条 A/B trace 双后端首次在设备上以独立 apply 线程渲染;**barrier tax 首测**(split − push 逐线程 CPU p50)+5.9% – +18.2%。 +- 唯一收官审查 0 blocker / 2 major / 1 minor,定向修复。本页末尾归档了 P5 / P5b 出口时记下的债务表(2026-09-22 口径)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5b** `inproc` verb migration +- **状态**:✅ `37fc4fdb..82683d4a` +- **落地什么 / 范围**:class-C 普查决定顺序;d1 19 / i1 7 / t2 6 / f1 11 槽迁移;sync 五槽;具名 blit;GLES mip descriptor;r1 / r2;收官审查三项修复。发射表 A=2 / B=54 / C=15 +- **验收门 / 证据**:主机全门 `348d22a4` complete;`integration-split` 107/107;合并普查零回退、79 trace 72/6/1;**Redmi 正确性 8/8 + 四臂 A/B、barrier tax 首测**。§7 + +## 实测:P5b(主机门 `348d22a4`,设备源头 `82683d4a`)(原 `MEASUREMENTS.md` §7) + +### 7.1 迁移与包证据 + +`348d22a4` 含 d1 / i1 / t2 / f1、r1 / r2、五槽 sync、具名 blit 与 GLES mip storage;发射表 A=2 / B=54 / C=15(目录 76 条,槽数与 opcode 数不是同一统计量)。 + +| 包 | 已落地行为与证据边界 | +|---|---| +| d1 | 19 个索引 / 实例 / multi-draw / indirect 槽经 `draw_vbo` 过线;独立包普查 77 个 Minecraft 后端用例 28 passed、49 first blockers,全部越过原 draw 首阻塞(默认 stage 32 MiB) | +| i1 | 七个 image / compute / barrier / copy-image / storage-block 槽;原首阻塞 239 归零;CopyImage 的 client-shadow 镜像被跳过,`GetTexImage` 前端回退仍可能读旧 shadow | +| t2 | 六个 XFB / 曲面细分槽;原首阻塞实测 140(`BeginTransformFeedback` 95 + `PatchParameteri` 43 + `BindTransformFeedback` 2);包内基线 490 / 191 / 390 / 78,原 432 passes 全保留 | +| f1 | 11 个 clear / copy / mip 槽;原 34 个首阻塞归零;bound named-clear 接线,unbound 仍具名拒绝 | +| r1 / r2 | P5 收官 #1–13;r1 定向 10 passed / 0 skipped;client-only push suppression 两个像素控制变红 | +| 具名 blit | `BlitNamedFramebuffer` 经 scoped read/draw binding 发布,退出恢复公开绑定;两后端四个 split 像素 entry 通过 + 15 unit | +| GLES mip storage | server 只验证 applier descriptor 的 Levels/extent;三个 mip 像素控制 3/3;iris-BSL GLES 单例 SSIM 0.997496(默认 stage 32 MiB);registry 只 Find 不 mint(barrier 债);RGB 三通道 CPU fallback 保持 Fatal | +| sync | 五条现有 opcode 接线,wire 只传句柄,native fence 归 apply 线程;原 LOCAL guard 使 trace 的 `FenceSync` 没真正执行,所以 d1 旧普查里没有可扣除的 FenceSync 首阻塞数 | + +包报告:`~/w7/notes/p5b/p5b-results/{d1-codex-v1,i1-v1,t2-codex-v1,f1-v1,r1-codex-v1,r2-v1,blit-codex-v1,mip-codex-v1,sync-codex-v1}.md`。 + +### 7.2 主机收尾门与合并普查(`348d22a4`,`~/w7/p5b-final-host-348d22a4/`;同名重跑 @ `3c80cd62` 见本节末) + +| 车道 | Selected | Passed | Skipped | Failed | +|---|---:|---:|---:|---:| +| unit-linux / unit-push / unit-verify | 1817 × 3 | 1500 / 1782 / 1795 | 317 / 35 / 22 | 0 | +| unit-split | 2132 | 2122 | 10 | 0 | +| gpu-linux-monolith / gpu-push-monolith / gpu-split-monolith | 1148 / 1148 / 1178 | 895 / 953 / 953 | 253 / 195 / 225 | 0 | +| split(`integration-split`,inproc) | 107 | 105 | 2 | 0 | +| verify / audit | 950 / 10 | 804 / 9 | 146 / 1 | 0 | + +G1 27,814 符号 0/0/0/0、`.text` 10,806,611 → 10,806,611;G5 两族 + pin 自测;生成器 / 纯度检查;G2 / G14 rc=0;E1 / E3(a)(精选 4 case)rc=0;E2 OpenRA 2/2、draw-drop SSIM 0.000036 / 758 records、pull-library 拒绝与库 SHA 恢复一致;smoke 16 core + 12 private。retrace-push **79/79**;retrace-verify 在 WSL 重启前完成 4/79,其余 75 条以 SHA-256 钉住的同一 verify 库串行续跑,合并 **79/79**;对账无缺步、无非零退出(`reconciliation-20260916.md`),`complete=true`、exit 0。一次 shell 异常(追加 runner 行触发 `-test-dir: command not found`)保留在原始输出,不冒充测试失败。 + +**P5b 基线。** 合并 inproc 普查(`~/w7/p5b-final-census-348d22a4/`):integration lane 显式 32 MiB,**1267 selected = 811 passed / 203 skipped / 62 aborted / 191 failed**;旧 1149 名全部保留、新增 118;旧 432 passes 全保留、零回退;旧 505 abort → 265 passed / 18 skipped / 58 aborted / 164 failed;旧 27 failed → 1 passed / 26 failed。完整 trace 显式 256 MiB(容纳单次 128 MiB 上传,不是默认容量修复):**79 = 72 passed / 6 aborted / 1 failed**(主跑止于 73/79,三轮续跑补齐;`create-indirect` DirectVulkan 在 llvmpipe 上内存膨胀 >60 GiB RSS 被守护杀死,两次复现,记 failed)。7 条未过项首阻塞:`rd12` DirectGLES `Fatal{InitialBytesNotCarried,"resource_respecify"}`、DirectVulkan `Fatal{BarrierTimeout,"Present"}`;`iris-photon`、`iris-derivative`、`create-indirect` 三个 DirectGLES `Fatal{UnmigratedEmulation,"texture-remint-pull"}`;`iris-bsl-esc-menu-854` DirectGLES `InitialBytesNotCarried`;`create-indirect` DirectVulkan 内存守护。通过项含 `improved-transparency-minecraft-26.3` DirectGLES SSIM 1.0 / DirectVulkan 0.999914、`iris-iterationrp` DirectVulkan 0.995833、`iris-bsl-esc-menu-854` DirectVulkan 0.998402。逐 trace 见 `joint-codex-v1.md` 的 census 块与 `identity.json` / `counts.json` / `trace-transitions.json`。 + +**同名重跑 @ `3c80cd62`(P7 wave 3 包 E1,2026-09-22,WSL `~/w7/p7-census-e1`,lavapipe)。** 方法逐字沿用上段普查的 helper(`~/w7/p5b-final-census-348d22a4/artifacts/census-helper.py`):取 monolith 条目的命令与 ENVIRONMENT,`MOBILEGL_IPC_STAGE_MB=32` 与 `MOBILEGL_ITEST_REQUIRE_GPU=1` 作环境底、条目自己的 ENVIRONMENT 覆盖之,再强制 `MOBILEGL_TRANSPORT`、私有 `MOBILEGL_LOG_FILE_PATH`、tcache 关,状态映射相同;名单 = 上段 1267 个名字(头上 0 个缺失)。跑器 `~/w7/e1bin/{wa27,walane}.py`,证据 `~/w7/e1-census/{wa27,lane-inproc2}-3c80cd62/`;lane 普查单并发墙钟 426 s。 + +| 读数 | P5b `348d22a4` | 头 `3c80cd62` | delta | +|---|---|---|---| +| 27 个 wrong-answer 同名,inproc | 1 passed / 26 failed | **24 passed / 3 aborted / 0 failed** | 23 转绿;3 条由错答变为具名停止 | +| 同上,spawn(P5b 未测) | — | 24 passed / 3 aborted / 0 failed | 与 inproc 逐名相同 | +| 合并 lane 普查 1267 同名,inproc | 811 / 203 skipped / 62 aborted / 191 failed | **1051 / 172 / 37 / 7** | failed→passed 187、aborted→passed 62、skipped→passed 31;passed→非绿 40、failed→aborted 4 | +| trace(CI split 子集),inproc | 79 = 72 / 6 aborted / 1 failed(含 `rd12` × 2,256 MiB) | **77 = 77 / 0 / 0**(默认 32 MiB;spawn 同为 77 / 0 / 0) | `rd12` 已 `ci: false` 出分母;其余 7 格全活([`notes/p7/iris-census-3c80cd62.md`](../p7/iris-census-1135c664.md) §6) | + +27 条按 §6.2 的家族: + +| 家族 | 条 | 头(inproc = spawn) | 机制 / 读法 | +|---|---:|---|---| +| `LayeredAttachmentShapeScenario` | 14 | **14 绿** | 未二分到提交;DirectVulkan 的 7 条在 `integration-magma-full-{split,spawn}` 的同名注册条目上也绿 | +| packed depth/stencil `GetTexImage` | 3 | **3 绿** | DirectGLES `Split/Spawn.ForcedDs` 与 DirectVulkan `Split/Spawn.Full` 的注册条目同绿 | +| framebuffer `HandleRecycleScenario` | 5 | **2 绿**(DG `Handles`、DV `AbaControlHandles`)/ **3 具名停止** | 停止的 3 条都是 `integration-monolith-control` 里 `MOBILEGL_PIPE_PUSH=0` 的对照臂:DG `Legacy` → `Fatal{PipeLegacyMemosDisabled, "MOBILEGL_TRANSPORT is not monolith and kMGPipeSubsystemSamplers (bit 11) is clear …"}`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:4289` 经 `StopOnArmlessPipeSubsystem`,`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2915-2918`);DV `Legacy` / `AbaControl` → server `Fatal{UnmigratedVerb, "Magma:clear-framebuffer-record"}`(`MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp:8008`:push 全关时 transport 下没有 framebuffer record),spawn 臂 client 另记 `Fatal{ReadbackDeclined, "ReadPixels"}`。旋钮矛盾的响亮停止,不是错答;push 打开的两条同名兄弟全绿 | +| `PrimitivesGeneratedNoXfbScenario` | 3 | **3 绿** | DV `Split/Spawn.Full` 的 `CountsADrawMadeWithNoCaptureSpan` 同绿(`TheRerouteIsActuallyArmed…` 在 full 车道因不钉旋钮而 skip,按设计) | +| `TextureParamsWithoutASamplerView` | 1 | **绿** | DG `Split` / `Spawn` 注册条目同绿——ROADMAP 债务表的「待核」关闭 | +| `P4aFinalFixScenario` FBO/RBO delete | 1 | **绿** | 与 P5b 同 | + +lane 的 44 条非绿(37 aborted + 7 failed)**全部是 `integration-monolith-control` 条目、且自己的 ENVIRONMENT 钉了非默认 `MOBILEGL_PIPE_PUSH`**(0 / 0x7f / 0x1ff / 0x5ff / 0x9ff / 0x1fff / 0x8000000000001fff)——普查方法把 monolith 对照臂也强制上了 transport,这 44 条读的是「这组旋钮在 split 下没有臂」:35 条具名停止(18 × `Magma:clear-framebuffer-record`、17 × `PipeLegacyMemosDisabled`),2 条 `Fatal{UnmigratedPipeInput, "GetBufferBindingPoint@DispatchCompute"}`(掩码 0x1fff),7 条断言失败在单体专用仪器上(4 × `CsoContentAddressingScenario` 在 client 日志里找带 `cso[csom= csob=]` 的统计窗口,而 split 下 CSO 在 server 铸造、那一行在 server 日志;3 × DV `HandleRecycle.AbaControlHandles` 的 buffer / VAO 用例是负控,期望 STALE,wire 路径读到 FRESH——即像素正确、负控不起作用)。这 44 条之外的 1223 条全部 passed 或 skipped。其中 40 条在 P5b 是 passed,头上转为上述读法;没有一条是默认旋钮下的产品回归。 + +### 7.3 唯一收官审查与定向修复 + +审查 `p5b-close-codex-review.md`(`348d22a4`,diff base `37fc4fdb`,只读):**0 blocker / 2 major / 1 minor**。 + +| 项 | 问题 | 处置 | +|---|---|---| +| Major 1 | user-index span 在段内但 Size 可能短于 Count × IndexSize | `a021e3cc`:encoder / decoder / sink 共用 shape/extent gate(单 range、宽度 1/2/4、`Uint64(Count) × IndexSize ≤ Size`),三端短 span 均拒绝、段末 exact-fit 通过;定向 9/9 | +| Major 2 | `ClientWaitSync` 保留 64-bit timeout 但 applied/reply 等待一律 30 s | `82683d4a`:预算 = ceil(timeout ns / 1e6) + 30,000 ms,有限 chunk;普通容量等待 / `FenceWaitServer` 仍 30 s,shutdown 可唤醒。**该 30 s 常量后来在 CI 修复轮改成 120 s(`68b55ea3`),原因见本表下注** | +| Minor 3 | ReadPixels exact-size reply 的未知 status 穿过 production helper | `82683d4a`:tight 与 bounce 路径拒绝 status=3,`Fatal{ReplyStatusInvalid}` | + +定向 `RemoteClientTest` 7/7(0 ns、1 ns、60 s、UINT64_MAX 预算,`FenceWaitServer`、shutdown、未知状态)。合并后 quickgate 里 split 单元车道仅有的 2 个失败(`PipeWireCodecTest.UserIndexSpan*`)是测试日志捕获缺陷(库按进程截断日志,fork 子进程增量读读空),`7cb29d46` 修复后 3/3。本阶段不再做第二轮全门或审查;原始全门源头仍为 `348d22a4`,三项修复只以定向证据补齐。 + +> 上表 Major 2 的 30 s 是 `82683d4a` 当时的值,保留为历史记录。**当前值是 120 s**(`68b55ea3`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`),因为 lavapipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,30 s 的看门狗会把合法慢路径间歇判红;详因见 `ARCHITECTURE.md` §17.1。`RemoteClientControls.FenceWaitBudgetHonorsGlTimeoutAndFiniteTransportChunks` 的期望值(30000/30001/90000)已同步改为 120000/120001/180000。 + +### 7.4 Redmi 出口与四臂 A/B(`82683d4a`,APK `p5bcodex2`) + +Redmi `2f7cbe2e`,证据根 `MobileGL/.trace-work/p5b-redmi/p5bcodex2/2f7cbe2e/`;全部组合显式 `MOBILEGL_IPC_STAGE_MB=256`。**钉频口径变更**:2026-09-11 的厂商 GPU 上限(1050 MHz)已消失,本次 deterministic pin 为 **1100 MHz**;与 1050 MHz 时代的活动不可比钟频,只有同场四臂配对可比。 + +**正确性 8/8**:`improved-transparency-minecraft-26.3`、`minecraft-1.21.4-in-world`(GLES SSIM 0.999995)、`minecraft-1.21.4-fabric-sodium-in-world`、`minecraft-1.21.4-fabric-iris-bsl-in-world` × 双后端,inproc split 臂,逐组钉频取证 + PNG / SSIM 阈值 + inproc + apply 线程 + 正 wire 记录三证,skip 即失败。**四条 A/B trace 首次在 Redmi 上以独立 apply 线程渲染——P5b 出口判据达成。** + +**四臂 A/B(barrier tax 首测)**:32 组中 24 组带 200 帧尾验证全绿;三次重复取 wall-time 均值最低者、尾 200 帧(`ab-tables.md`)。逐线程 CPU p50: + +| trace | 后端 | push−pull | **barrier tax(split−push)** | barrier tax 帧 p50 | +|---|---|---:|---:|---:| +| improved-transparency-26.3 | DirectGLES | +15.6% | **+10.3%** | +0.1% | +| improved-transparency-26.3 | DirectVulkan | +8.8% | **+13.1%** | +13.3% | +| minecraft-1.21.4-in-world | DirectGLES | +21.4% | **+18.2%** | +0.2% | +| minecraft-1.21.4-in-world | DirectVulkan | +11.8% | **+17.9%** | +19.7% | +| minecraft-1.21.4-fabric-sodium | DirectGLES | +10.9% | **+5.9%** | −0.1% | +| minecraft-1.21.4-fabric-sodium | DirectVulkan | +7.6% | **+8.3%** | +4.3% | + +splitctl−push 全部在 ±1.6% 内(增量来自 inproc 传输与 barrier,不是 APK / 构建差);p99 同向放大(sodium DirectVulkan 帧 p99 +107.7% 为离群尾帧)。inproc 臂 peak RSS 389 MiB – 2.5 GiB,全角色映射 560.5 MiB(256 MiB × 角色视图是虚拟映射容量,不是 RSS 增量)。 + +**fixture 受限的 8 组**:`iris-bsl-in-world` 只有 123 个 benchmark 帧,低于 200 帧尾规则,四臂 × 双后端验证全部失败(pull 同失败,是 fixture 上限不是回归);123 帧完整序列按同法折算成补充表 `ab-tables-bsl-123frame-supplement.md`:barrier tax CPU p50 DirectGLES **+7.1%**、DirectVulkan **+9.1%**,不混入上表。性能仍只记录、不作阻塞门。 + +## 落地形状(原 `ARCHITECTURE.md` §17.5) + +### 17.5 P5b:class-C verb 迁移 + +- **顺序由动态普查决定**(ID-68):79 个 trace × 集成车道在 inproc 下的首阻塞统计(`~/w7/notes/p6/census-classC.md`),所有 Minecraft trace 首阻塞 = `DrawElements` → Minecraft 优先,四包并行(d1 draws、i1 image/compute、t2 XFB/tess、f1 clear/copy/mip)+ 尾包(sync、具名 blit、GLES mip)。 +- **一次迁移 = 一个发射器 + 一个 sink 体**:c0b 先给 25 个已测量槽铺 wire 记录(`Wire/PipeWireCodec` 的行)、`WireVerbSink` 分派与 `ServerVerbSink` 具名 Fatal stub;包只在 `EmitTables.cpp` 把槽从 C 挪到 B、在 `PipeApplier.cpp` 填 sink 体(apply 线程、只从记录与 server 状态取输入)。这些槽到达的是 sink,不是 `MGPipeApply*`。 +- **规则 D**:记录把 GL 调用逐字带在句柄旁边(draw 的 mode/count/type/offset/instance/base 字段、clear 的值类与 drawbuffer 下标、named-framebuffer 的句柄形式),后端保留它的 barrier-pulled 读;未测量的形式**按名拒绝**(`+RENDERBUFFER`、`+UNBOUND` 等)而不是猜。 +- **draw 家族**:19 个索引 / 实例 / multi-draw / indirect 槽全部下沉到 `draw_vbo`;用户索引 span 的 shape/extent 门在 encoder / decoder / sink 三端共用(单 range、宽度 1/2/4、`Uint64(Count) × IndexSize ≤ Size`)。 +- **sync**:`FenceSync` / `ClientWaitSync` / `GetSyncStatus` / `WaitSync` / `DeleteSync` 五条现有 opcode 接线,client 铸造 Fence 句柄,wire 只过 `{slot,gen}`,native fence 归 apply 线程;顺带修掉 `MGL_BACKEND_SLOT_PTR_LOCAL` 对 split 恒返回 nullptr。 +- **具名 blit**:`BlitNamedFramebuffer` 经作用域化 client-shadow read/draw 绑定发布,降为现有 bound backend 调用,退出恢复公开绑定。**GLES mip storage**:前端先定义并发布层级,server 只验证 applier descriptor 的 Levels/extent;registry 身份解析只 Find 不 mint。 +- **P5b 留下的 inproc 依赖**:具名 blit 的 scoped client binding + barrier;mip descriptor 的 barrier-held registry 查询;FBO death 的 inproc mailbox。三者跨地址空间都不成立——它们只是 §17.6 清单里的三行。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P5b 出口(2026-09-16,已达成)**:主机全门 `348d22a4` complete;79 trace 72/6/1;设备源头 `82683d4a`(APK `p5bcodex2`)Redmi 正确性 8/8——四条 A/B trace 双后端首次在设备上 inproc 渲染;barrier tax 首测 split−push 逐线程 CPU p50 +5.9% – +18.2%。 + +## 债务表全文(原 `ROADMAP.md`「P5 / P5b 出口记录的债务」,2026-09-22 口径) + + +| 债务 | 去向 / 当前口径 | +|---|---| +| P5 的 27 个普通 inproc wrong-answer(历史计数) | **已结(2026-09-22,`3c80cd62` 同名重跑,`MEASUREMENTS.md` §7.2;inproc 与 spawn 逐名相同)**:原「22 → P4b/P7 texture readback」→ **19 绿**(14 layered、3 packed depth/stencil、2 framebuffer recycle),余 **3 条 framebuffer recycle 由错答变为具名停止**——三条都是 `integration-monolith-control` 里 `MOBILEGL_PIPE_PUSH=0` 的对照臂(DG `Legacy` → `Fatal{PipeLegacyMemosDisabled}`;DV `Legacy` / `AbaControl` → `Fatal{UnmigratedVerb, "Magma:clear-framebuffer-record"}`),旋钮在 split 下没有臂,不是 readback 欠账;原「3 → P7 query」→ **3 绿**;1 inspection(`TextureParamsWithoutASamplerView`)→ **绿,「待核」关**;1 FBO/RBO delete → 绿。同一重跑的 1267 名合并 lane 普查 811/203/62/191 → 1051/172/37/7,44 条非绿全是钉了非默认 `MOBILEGL_PIPE_PUSH` 的对照臂 | +| `rsp` residual inputs | **P5f 已关闭残余读取**:字段归属 41 record / 6 derived / 0 barrier / 16 fatal;strict 与双块 marker 表均为空,双后端逐帧 RSP 门 2 PASS / 0 skip,Redmi 36 个统计窗口 rsp=0。P5e 的“rsp 只计被采纳设障 pull”是历史计数规则,八对 admitted 的历史观察不再是当前允许集合 | +| `SEG_REPLY` 2 MiB 单槽 payload cap | → **P9**(契约 §10.2.3 改判,ID-47);stream 链路上 reply 本就是消息,cap 变成 P6.5 nd 的 `maxReplyBytes` 窗口 | +| `GetCaps` 的两个 blobref | 目前不骑 record;一旦运输,必须有 server→client carrier rule,不能套 `SEG_STAGE` | +| PACK-PBO readback | P5 曾具名拒绝;`70df57bb`(2026-09-20)起 **client 侧已实现**(bounce 紧凑 reply + 按 PACK 步长逐行 `UploadSubData`,`EmitTables.cpp`;`glGetTexImage` 同形且按 `MaxReplyBytes` 分块);剩余只是 fire-and-forget(`MarkReadPixelsPackBuffer` 全仓无生产调用者)→ **P9**(ID-57) | +| ABI fingerprint | **已由 P6.5 wf / nd 落地(`fe28bdb5`)**:`wireFingerprint` 布局摘要 + `buildFingerprint` 拆分(只在 `Dial==Fork` 或 `MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 下比较)、`LinkTerms` 四个尺寸由 server 陈述、`DynamicBackendParameters` 定宽;`CONTRACT-P6.md:666` 仍把定宽记在 P7 名下,待改 | +| 默认 32 MiB staging | 目标负载单次 128 MiB 上传装不进默认 stage;**内容侧分块已落地**(buffer 范围走查 `1e7c372e`、纹理整宽 slab `9469d48e`,预算 `MGPipeStageChunkBytes()` = 默认 segment/4 = 8 MiB),故普查 / Redmi 的显式 256 MiB profile 不再是这两条路径的必需;未接入分片的 record 类型与专用 carrier 仍归 P8(开放问题 11) | +| P5b 的 inproc 依赖 | 历史的 scoped client binding、mip registry 查询与 FBO death mailbox 已经 P5c/P5f 改为记录、句柄和控制/事件路径;P6 核验并装配已有载体,不再重做这些迁移 | +| 未迁移与仿真路径 | **class-C 已归零到两个**(2026-09-22 核):`EmitTables.cpp` 的 `MGR_UNMIGRATED_*` 列表全空,只剩手写的 `SetSwapInterval`(P10)与按裁定无行的 `DeleteTransformFeedback`(P9);契约 §11 / SPAWN-PLAN §8 的"511 class-C entries"是 P5 joint 树上的旧普查(`notes/p6/census-classC.md`),不是当前数;~~client vertex arrays~~(已实现:`beba0256` 起 wire 复制到 owned buffer 并保留 baseVertex/baseInstance/drawID,本轮补齐 monolith 臂 `62bfe461..e258a822`)、multi-draw client indices、RGB 三通道 CPU mip、renderbuffer copy endpoint、未绑定 named clear、`texture-remint-pull` 仿真保留具名拒绝 → P8 / P9;Magma split compute / image 的“82 个错答”是 P5b 历史普查计数,不是当前失败清单;P5f 后续已补应用 buffer consumers 与 Android 旋转 blit,Magma inproc 已能运行所测 MC 世界;后续 run-ahead 已完成([报告](../p5f/magma-runahead.md));XFB、部分不对齐范围、placeholder/native-format 及进一步性能工作仍归 P7([报告](../p5f/magma-inproc-fix.md)) | **P5e 补 → CI 收尾更正**:client vertex arrays 曾是 run-ahead 下的具名拒绝(ID-82)与预期红车道 `integration-clientarrays-split`(ID-134);`beba0256` 起已实现,`347bb90c` 已把该车道翻转为**硬绿门**并纳入主 GPU/split 标签,本轮补齐 monolith 臂(`62bfe461..e258a822`);staging 仍归 P8 +| E2 wire 内容控制 | draw-drop 是 OpenRA 的有效控制(758 draws,SSIM 0.000036);clear-drop 被全屏 overdraw 掩盖,不能作控制 | +| max record bytes | 实测 reduced / OpenRA `maxrec=784 B`,默认 cap 4 MiB;只代表所测负载,后续索引 / indirect 尾仍须记录 | +| 树外脚本 | 普查跑器、`wsl_p5_gate.sh`、Redmi 定频行都在 `~/w7/notes/`,git merge 不会传播 | +| 临时 CI trigger | 合入 dev 前删除 `test.yml` / `apk.yml` 的 `feat/disaggregated` TEMPORARY trigger | + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P5B.md`](BRIEF-P5B.md) | BRIEF-P5B — verb migration under inproc, Minecraft-first | +| [`PACKAGE-PREAMBLE.md`](PACKAGE-PREAMBLE.md) | P5b 包通用前言 — 每个包开工前必读 | +| [`p5b-results/`](p5b-results/) | 17 个文件:各包(d1 / i1 / t2 / f1 / r1 / r2 / blit / mip / sync)的报告 | diff --git a/docs/Disaggregated/notes/p5c/README.md b/docs/Disaggregated/notes/p5c/README.md new file mode 100644 index 00000000..0d30e54c --- /dev/null +++ b/docs/Disaggregated/notes/p5c/README.md @@ -0,0 +1,114 @@ +# P5c — `inproc` 共享内存读点归零(`11ac3de6..b88e8487`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 起因:对 `a79a0af6` 的只读审计([`p5c-audit-v1.md`](p5c-audit-v1.md),59 行)证明 P5 / P5b 的 `inproc` 仍有一批不经任何载体、只靠 verb barrier 与同一地址空间才正确的直接读写。P5c 让 `inproc` 成为只经 wire 交换的诚实两角色,从而 P6 只是传输替换。 +- 落地:server 端纹理 staged shadow(`StagedTextureStore`);`SEG_EVENT` 三个 producer + `kEventGlError`(反向通道零裸指针);sink / twin 按记录句柄解析;`applier_reset` / `object_death` 控制记录(opcode 77 / 78);`set_context_values`(79)退役值类 BARRIER-PULLED 行;双层 `Fatal{RoleViolation}` 守卫;两个具名豁免 scope 承接未到期的债。契约 `MobileGL/MG_Remote/CONTRACT-P5C.md`。 +- 门:守卫开启下 unit 2187/2187、`integration-split` 111/111,每层守卫 red-once;纹理 `0xDD` audit 零 Fatal;G1 三个认定 resize + `.text` −16 B。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5c** `inproc` 共享内存读点归零 +- **状态**:✅ `11ac3de6..b88e8487` +- **落地什么 / 范围**:server 端纹理 staged shadow(`StagedTextureStore`,句柄为键、整段覆盖、defined-ness 追踪);`SEG_EVENT` 三个 producer + `kEventGlError`(反向通道零裸指针);sink / twin 按记录句柄解析(blit 具名臂经 verb 句柄工作区,Magma 走 G6 消费臂);`applier_reset` / `object_death` 控制记录(opcode 77/78,framebuffer 首个 wire delete);`set_context_values`(79)退役全部值类 BARRIER-PULLED 行,三 shutter 自答;双层 `Fatal{RoleViolation}` 守卫 + `InBarrierWait` 接线;两个具名豁免 scope(通告家族 P4b/P7、G6 registry 家族 P3b/P4b)承接未到期的债 +- **验收门 / 证据**:角色守卫开启下 unit 2187/2187(strict 下同绿)、`integration-split` 111/111、每层守卫 red-once 按名变红;纹理 `0xDD` audit 在 bsl / complementary 两条 in-world trace 零 Fatal;rsp 按帧实测(bsl 948.5/帧,残留全为钉住的对象类 15 行);G1 三个认定 resize(SwapchainObject::Create / CopyTexSubImage2D / ScopedRestartIndexSubstitution,均已具名)+ `.text` −16 B、pull 构建零 MG_Remote 符号;G5 保护区字节一致;双生成器 --check/--self-test 绿 + +## 历史:P5c 计划与审计(`inproc` 共享内存读点;审计头 `a79a0af6`,2026-09-17) + +**为什么插在 P6 之前**:P5 / P5b 的 `inproc` 只把 verb 记录、`SEG_STAGE` blob、reply 与 caps 快照做成了 wire。对 `a79a0af6` 的只读静态审计(`~/w7/notes/p5c/p5c-audit-v1.md`,59 行清单,逐条在该头上重新解析)证明两个角色之间仍有一批**不经任何载体的直接内存读写**,靠 verb barrier 与同一地址空间才正确。它们不是传输问题:`spawn` 换掉的只是 ring 的传递方式,而这些读写在另一个进程里根本没有对应内存。P5c 的目标是让 `inproc` 诚实——两线程之间除 `SEG_CMD` / `SEG_STAGE` / `SEG_REPLY` / `SEG_EVENT` / caps 快照 / 控制帧之外零直接访问——从而 P6 只是传输替换。**本节保留 P5c 启动时的计划与审计语境;P5c 已收官,后续对象类、控制帧、静态量与反向通道的收口结果以 P5f 报告为准。** + +### 审计清单(按家族;`file:line` 以 `a79a0af6` 为准,完整 59 行见报告) + +| # | 方向 | 直接访问的是什么 | 站点 | 今天为何能工作 | 现有守卫 | P5c 载体 | +|---|---|---|---|---|---|---| +| T1 | S→C 读 | **纹理纹素**:`resource_subdata` 已把 level 字节放进 `SEG_STAGE`,但 `ApplyTextureUpload` 只做门与累积、把指针丢掉,Espryt 同步时从 client 的 `MipmapStorage` 重读纹素;纹理对象的形状与脏区也直接读前端对象 | 丢指针 `MobileGL/MG_Pipe/PipeApply.cpp:989-1008`;重读 `DirectGLES/Managers.cpp:7315`(`MapMipmapData`)、`:6940`、`:7129`、`:7198`;形状 `:7314`、`:7332`、`:6878-6880`、`DirectGLES/DirectGLES.cpp:8502-8507`、`:8528-8529`;脏区 `DirectGLES/Managers.cpp:7360`、`:7371` | barrier + 同地址空间 | **无**:整个纹理家族不在 `FieldOwnership.def`,`rsp` / `MOBILEGL_IPC_STRICT_ERRORS` / `MOBILEGL_IPC_AUDIT` 都看不见 | server 端纹理 staged shadow(buffer `StagedShadowStore` 的纹理半边);`SyncMipmapsToBackend` 改读它,形状与脏区读描述符 | +| T2 | S→C 读写 | client 的 slot 分配器 `MGPipeSlots()`:mip / CopyTex / twin 创建时按前端 `GetLifetimeId()` 在 client 分配器里查找甚至铸造 | `DirectGLES/DirectGLES.cpp:8087` → `DirectGLES/SlotTables.h:392-400`(`HandleOf` → `FindByLifetimeId`);铸造 `DirectGLES/SlotTables.h:232`(`GetOrCreate`)、`:418`、`:428`;`DirectGLES/Managers.cpp:2708`、`:4293`、`:5627`;`DirectGLES/DirectGLES.cpp:6706`、`:8185`、`:8564`、`:8659` | barrier;mip 只 Find 不 mint | 无(`MOBILEGL_ASSERT` 在 INFO 构建失效) | 记录已带句柄(`MGPMipPlan::Res`、`MGPCopyFromFramebuffer::Dst`、`MGPBlit::ReadFbo/DrawFbo`)→ `GetOrCreate(MGPipeHandle)`(`DirectGLES/SlotTables.h:278`) | +| T3 | S→C 读 | 具名 blit:client 临时改绑自己的 read / draw 绑定槽,server 经 `MGB_CTX` 读绑定槽 | client `MobileGL/MG_Remote/Client/EmitTables.cpp:741-786`;server `DirectGLES/DirectGLES.cpp:7782-7785` | barrier + RAII 恢复 | `rsp`(`GetFramebufferBindingSlot`) | 记录里的 `ReadFbo` / `DrawFbo`,sink 侧按句柄解析(`MobileGL/MG_Remote/Server/PipeApplier.cpp:228-244`) | +| T4 | S→C 读 | `CopyTexImage2D` / `CopyTexSubImage2D` 经纹理单元绑定槽取目的纹理 | `DirectGLES/DirectGLES.cpp:8561-8563`、`:8656-8658` | barrier | `rsp`(`GetTextureUnitObject`) | 记录里的 `Dst` 句柄 | +| T5 | S→C 写 | Magma 生成 mip 直接写 client 的 level 存储(Espryt 的重推导臂没有 Magma 对应) | `DirectVulkan/Renderer/VulkanRenderer.cpp:1562-1590`(调用 `:11317`) | barrier | 无 | 与 GLES 同形:只按描述符验证 | +| B1 | S→C 读 | `BufferObject::HasDefinedContent()`,每个 ensure 的 draw 都读 | `DirectGLES/Managers.cpp:3147-3148`(`EnsureBufferResourceForHandle`;`:3141-3146` 自述为债) | barrier | 无 | 描述符旁的 live-content 位,client 发布 | +| B2 | S→C 读 | `HandleOfBuffer` 每次 ensure 读前端 `GetLifetimeId()` | `DirectGLES/Managers.cpp:2705-2716` | barrier + memo | 无 | 记录里的句柄 | +| B3 | S→C 读 | 旧 buffer 臂(`MappedData()` / `IsMapped()` / `GetChangeSerial()`)在 split 构建里位 7 清零时可达,arm resolver 只打一行 `MGLOG_D` | `DirectGLES/Managers.cpp:2586-2620`;臂体 `:1012-1022`、`:1119-1121`、`:2944-2949`、`:3281-3327` | 同进程 | 无 | 有传输时具名拒绝(同 `Fatal{PipeLegacyMemosDisabled}` 的形状) | +| B4 | S→C 读写 | XFB:`SharedPtr` 跨整个 span 持有,结束时 `WritebackFromBackend`,scatter 对 client shadow 读改写 | `DirectGLES/DirectGLES.cpp:950`、`:1060`、`:1077`、`:1081`、`:1172`、`:1185` | barrier | 无 | `OnBufferWriteback` / `OnXfbScatterReady` 事件——P9 的题材,P5c 只记账 | +| R1 | S→C 写 | 反向通道是 apply 线程直接调进 client 的 `MG_State`:`OnBufferWriteback` 把**裸指针**塞进 `MGPBlobRef.Offset`,client 侧再转回指针;consumer 会拒绝任何真实段 | 生产 `DirectGLES/Managers.cpp:2339-2343`(`Ops_H_Readback`);消费 `MobileGL/MG_Impl/Pipe/ResourceTracker.h:553-572`(拒绝真实段 `:560-564`) | 同地址空间 + barrier | 无 | `SEG_EVENT` `kEventBufferWriteback`:consumer 已在(`MobileGL/MG_Remote/Client/ClientSession.cpp:176-267`),**全仓零 producer**(`MobileGL/MG_Remote/Server/ServerSession.cpp:484-492` 无调用者) | +| R2 | S→C 写 | `OnGpuWritten` 直接调进 client tracker;Magma 更是绕过回调直接 `MarkGpuWritten()` | Espryt `DirectGLES/Managers.cpp:2729-2752`,喂入 `DirectGLES/DirectGLES.cpp:568`、`:616`、`:2601`;Magma `DirectVulkan/Renderer/UniformManager.cpp:1075`、`:1231`、`DirectVulkan/Renderer/VulkanRenderer.cpp:11618` | 同地址空间 | `MGLOG_E_ONCE` | `SEG_EVENT` `kEventGpuWritten`(consumer `MobileGL/MG_Remote/Client/ClientSession.cpp:227-236`) | +| R3 | S→C 写 | 默认 framebuffer 附件(`pDefaultFramebufferInfo`)由 server 在 surface 创建时写 | `DirectGLES/DirectGLES.cpp:11519-11620`;Magma `DirectVulkan/Renderer/SwapchainObject.cpp:276-335` | barrier;罕见 | 无 | `SEG_EVENT` `kEventSurfaceChanged`(consumer stub `MobileGL/MG_Remote/Client/ClientSession.cpp:242-253`) | +| R4 | S→C 写 | sticky forward:`RecordError` 写 client 错误队列;`InvalidateCompileEnv` 写 client 编译环境(在 stamped verb 之外静默 no-op) | `MobileGL/MG_Impl/Pipe/PipeFill.cpp:1753-1756`、`:1764-1772` | barrier | `rsp`;strict 下 Fatal | `RecordError` → `OnGlError` 事件(顺序保证是 P9 的);`InvalidateCompileEnv` 的活已由 R-12 caps 重发布完成(`MobileGL/MG_Remote/Client/CapsMirror.cpp:78-80`),删 forward | +| R5 | — | `MGPipeCallbacks` 十个回调只装了两个,且都是 apply 线程内联调用;`SEG_EVENT` 段已创建、映射、握手公告、每个 reply verb 后排空,但没有任何生产者 | 安装 `MobileGL/MG_Impl/Pipe/ResourceTracker.h:606-613`;段 `MobileGL/MG_Remote/Server/ServerSession.cpp:285-301`;排空 `MobileGL/MG_Remote/Client/ClientSession.cpp:836` | — | 溢出 latch 与 `eventDropped` 已建未用 | 本身就是载体 | +| C1 | S→C 读 | Magma 四处直接读 client 镜像 `pActiveBackendObject->GetDynamicParameters()`(Espryt 已改走 server 自己的 backend);Espryt 的格式表回落臂在 server backend 为空时读 client 镜像 | `DirectVulkan/DirectVulkan.cpp:713-715`;`DirectVulkan/Renderer/VulkanRenderer.cpp:667-676`;`DirectVulkan/Renderer/VertexInputStateFactory.cpp:249-250`、`:502-503`;`DirectGLES/Utils.cpp:50` | 镜像值等于快照 | 无 | server 自己的 `Backend()->GetDynamicParameters()`;回落臂拒绝 | +| G1 | shared | `gPipeInputs`:client 残余填充写、applier 写、server 的 36 行 BARRIER-PULLED 读 | `MobileGL/MG_Backend/MGPipe/PipeInputs.h:806`;填充 `MobileGL/MG_Impl/Pipe/PipeFill.cpp:2798`;applier `MobileGL/MG_Pipe/PipeApply.cpp:1415-1560`;stamp `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp:103-132`;`rsp` `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp:72` | **只靠 verb barrier** 保证单写者 | `rsp` + strict Fatal + poison | 值类行(`GetActiveTextureUnit`、`GetMaxTouchedTextureUnit`、`GetTouchedBufferBindingPointCount`、`GetCurrentVertexAttribute`、XFB 六项、三个世代)→ 每 verb 一条残余值记录或按 `Coverage.def` 由 server 自答;**对象类行**(`GetBoundVertexArray`、`GetProgramForDraw/Dispatch`、`GetTextureUnitObject`、`GetImageTextureBinding`、`GetFramebufferBindingSlot`、`GetBufferBindingSlot/Point`、sticky `GetTextureObject` / `GetProgramObject`)返回前端对象指针、无法序列化,留 P3b/P4b/P7/P8 的 twin 表 | +| G2 | C→S 写 | GL 线程在 `FreshlyPrimed` 时整体 `MGPipeApplierReset()` server 的 `g_applier` | `MobileGL/MG_Impl/Pipe/PipeFill.cpp:2654-2656` → `MobileGL/MG_Pipe/PipeApply.cpp:1202-1299` | barrier 的后置条件(validate 时无在飞记录) | 无 | make-current 边上的 `applier_reset` 控制记录 | +| G3 | C→S 写 | 六种前端对象死亡:栈上结构体指针经 mailbox 送到 apply 线程(framebuffer 没有 wire delete opcode) | `DirectGLES/Managers.cpp:206-228` | 阻塞 + 同地址空间 | 无 | `object_death` wire 记录 | +| G4 | C→S 控制 | 十二个 `Server*` EGL forwarder 经 one-slot mailbox 传**函数指针 + `void*` 栈局部** | `MobileGL/MG_Remote/Client/BackendObject_Remote.cpp:125-264`;邮箱本体在 fc 前的 HEAD 上是 `ServerLoop.cpp:533-568`(PumpControlRequest)+ `:645-707`(RunOnApplyThread)——本行原引 `:543-598`,f0-egl F9 修正 | 阻塞 | `Fatal{ApplyThreadNotRunning}` | **已由 P5f 包 fc 帧化**(原记 P6):`SurfaceControlFrame` 纯值帧走同一阻塞通道(`Server/SurfaceControlFrame.h`、分发 `ApplySurfaceControlFrame`),wire 编解码与具名拒绝在 `Protocol/SurfaceOpCodec.cpp`;报告 `notes/p5f/fc-report.md` | +| G5 | C→S 读 | `ServerLoop::OnApplyThread()` 每条 routed 记录读;bring-up 直接读 `loop.Backend()` / `MGPipeGetResourceOps()` | `MobileGL/MG_Remote/Client/WireTables.cpp:71`;`MobileGL/MG_Backend/Init.cpp:100`、`:145`、`:164-170`、`:247` | 同进程 | — | client 本地角色标志;caps 快照的 cap 位(P6 顺手) | +| G6 | shared | 前端地址 / lifetime-id 键控的 Espryt twin registry;持前端裸指针的 memo(`ResolvedDrawBuffers::Entry::frontend`、三张 texture sync list);server 读 `pDefaultFramebufferInfo`;`g_rawDepthFetchSamplerState`;`ScopedDefaultUnpackState::s_synced` 从不按 context 世代重置 | `DirectGLES/Managers.h:309-377`、`:1185`;`DirectGLES/DirectGLES.cpp:61-62`、`:1958-1987`、`:2846`;`DirectGLES/Managers.cpp:5806-5812` | barrier / server 私有 | — | 历史移交给 P3b/P4b / P6;transport registry 与 `s_synced` 世代问题已由 P5f fr / fs 收口 | +| A1 | — | barrier 的 client 半边断言从未接线:`InBarrierWait()` 零调用点,只有 `ApplyThreadIsInsideApplier` 在 emit 时检查 | `MobileGL/MG_Remote/Client/ClientSession.cpp:880`、`:748-753` | — | — | 接上,或删掉契约里的这句声明 | + +已确认 wire-clean、不进 P5c:buffer 全家族(`Ops_H_*` 只拿句柄 + 记录 + `SEG_STAGE` 字节,`StagedShadowStore::Adopt` 复制,`Fatal{StageSnapshotTooNarrow}` 六处);`resource_respecify` 初始字节双向拒绝;persistent map 仿真档按块过 `SEG_STAGE`,`Fatal{RoleViolation}` 挡住 apply 线程;`CreateShaderState` 归档是一个 blob;caps 的格式表与 renderer 字符串走 `CapsSnapshot` 控制帧(`GetCaps` 记录本身已死,只是卫生项);tight ReadPixels;`DrawVbo` 用户索引;fence 家族;`gMGPipeSegmentResolver` 与 `gBackendFunctionsTable` 的角色划分。 + +### 包与顺序 + +| 包 | 内容 | 关闭的行 | +|---|---|---| +| **c0c** 契约 | `MG_Remote/CONTRACT-P5C.md`:纹理 staged shadow 的所有权与覆盖规则;`SEG_EVENT` 记录与 blobref 约定(`Seg = kSegEvent`);sink / twin 按句柄解析的规则;`applier_reset` / `object_death` 记录;角色守卫的语义(有传输时 apply 线程触前端对象表面、GL 线程触 server 状态表面都是 `Fatal{RoleViolation}`);残余值记录的字段表;把纹理家族补进 `FieldOwnership.def` | — | +| **tx** 纹理 | `StagedTextureStore`:`ApplyTextureUpload` 采纳 `SEG_STAGE` 字节到 server 侧 level shadow;`SyncMipmapsToBackend`、形状读、脏区改读描述符与 shadow;Magma mip 改按描述符;`MOBILEGL_IPC_AUDIT` 的 `0xDD` 覆盖纹理 | T1、T5(T2 的 mip 半边随之) | +| **ev** 反向通道 | `ServerSession::PublishEvents` 接三个 producer(writeback / gpu-written / surface-changed),consumer 的段臂改为解析 `kSegEvent`;Magma `MarkGpuWritten` 改走回调;`RecordError` 进事件(顺序仍 P9);删 `InvalidateCompileEnv` forward;溢出策略沿用 `ARCHITECTURE.md` §11.7 | R1–R5 | +| **hd** 句柄解析 | blit / mip / CopyTex / `HandleOfBuffer` 的 sink 与 twin 按记录里的句柄解析(`GetOrCreate(MGPipeHandle)`),server 不再触 `MGPipeSlots()`;`HasDefinedContent` 改读描述符的 live-content 位;Magma 四处 caps 读改 server backend,`Utils.cpp` 回落臂拒绝 | T2、T3、T4、B1、B2、C1 | +| **ct** 控制记录 | `applier_reset` 与 `object_death` 两条 wire 记录(framebuffer 首次有 delete opcode);mailbox 只剩 EGL forwarder 给 P6 | G2、G3 | +| **rv** 残余值 | 值类 BARRIER-PULLED 行改为每 verb 的残余值记录(或 server 自答),`gen_pipe_field_ownership.py` 把它们改成 RECORD-SUPPLIED;对象类行保持 BARRIER-PULLED 并逐行标注归属阶段 | G1 的值类半边 | +| **gt** 门 | 角色守卫(两层 `Fatal{RoleViolation}`,仅 split 构建、有传输时生效);`InBarrierWait` 接线;旧 buffer 臂传输拒绝;`rsp` 按帧进 stats 行并在四条 A/B trace 上测得;CI 加一条 `MOBILEGL_IPC_STRICT_ERRORS=1` + 角色守卫的 `integration-split` 车道 | A1、B3,以及所有行的红一次证据 | + +顺序:c0c → tx / ev / hd 并行 → ct / rv → gt 收口。每包自带 red-once(R-16),阶段末一次 Codex / Kimi 审查(ID-66)。 + +### 出口门(E-P5c) + +1. **角色守卫下全绿**:守卫开启时 `integration-split`(107)、broad inproc 车道对 `348d22a4` 普查零回退、79 trace 无新增首阻塞;关掉任一层守卫必须能在一条具名用例上变红。 +2. **纹理字节不再回读 client**:`MOBILEGL_IPC_AUDIT=1` 对纹理 staged 字节的 `0xDD` 填充在四条 A/B trace 上无失败;把采纳改回丢指针必须变红。 +3. **`SEG_EVENT` 有生产者**:三条事件的往返单元与集成用例;排空点上 `eventDropped == 0`;把 producer 改回裸指针必须变红。 +4. **`rsp` 分类完成**:四条 A/B trace 每帧 `rsp` 已测;剩余读全部是对象类行,名单由 `FieldOwnershipTest` 钉住,值类行为 0。 +5. **Redmi 四臂复测**(记录,不设门):writeback / gpu-written 改走事件后 barrier tax 重新记录。 +6. G1 0/0/0/0、G2 / G14、G5 照旧。 + +### 历史移交项(P5c 当时不碰) + +P5c 当时移交的对象类 BARRIER-PULLED、transport registry 前端键、EGL 控制面、`s_synced` / `g_syncedRenderStateParameters` 世代与反向通道跨角色访问,已在 P5f 收口。P9 的异步化/排序、P8/P9 的协议广度与仿真功能仍按各阶段推进;不要把这份历史清单重新当成 P6 的帧化或静态量施工单。 + +## 实测:P5c(`11ac3de6..b88e8487`,同日收官)(原 `MEASUREMENTS.md` §8) + +出口门(E-P5c)实测: + +| 门 | 结果 | +|---|---| +| 守卫开启下 unit | **2187/2187**(strict `MOBILEGL_IPC_STRICT_ERRORS=1` 下同绿) | +| 守卫开启下 `integration-split` | **111/111**(107 + ct 的 4 条 CtWireScenario,全真跑) | +| 每层守卫 red-once | layer-1(MipmapStorage 守卫短路 → `TextureMapMipmapDataFromTheApplyThreadIsFatalByName` 红)与 layer-2(`RefusePipeInputsTouchWhileApplierOwnsIt` 短路 → `ClientPipeInputsFillWhileTheApplierOwnsItIsFatalByName` 红)各红一次并复原;tx 的采纳改回丢指针 → 恰 `StagedTextureProductionTest` 红;ev 的 writeback 改回裸指针 → `AtomicCounterScenario.SubDataAfterDispatchSurvivesAnImmediateReadback` 按名红;ct 的发射短路 → Ct 4/4 按名红;rv 的发射门短路 → 7 条按名红 | +| 纹理 `0xDD` audit | bsl in-world(100000 调用)与 iris-complementary in-world 全程零 Fatal | +| `SEG_EVENT` 往返 | 三种事件 + `kEventGlError` 各有单元往返;排空点 `eventDropped == 0` | +| `rsp` 分类 | 值类 = 0(rv 的 FieldOwnershipTest 钉住);按帧实测:bsl 948.5/帧(38.7/draw,123 帧)、complementary 1591.9/帧(151 帧)、iterationrp 286.2/帧(108 帧后止于既有具名 `texture-remint-pull`);残留全为对象类 15 行 | +| 普查(integration-gpu @ inproc,对同机 11ac3de6 基线逐名比对) | 基线 253 红 / 收官头 423 红;183 条 newly-failing 中 11 条为三个真回归(默认 FBO 格式时序、大 writeback 溢出、XFB scatter 读前端)——已修并回归绿;其余 172 条全部带 Fatal 名证据归类为设计红(传输下旧臂具名拒绝的对照车道 14 条 + Magma P7 未迁移面 156 条)与 2 条 DirectVulkan CtWire(改按名 skip:Magma 无 object_death 生产者,P7) | +| G1(pull 符号恒等,`11ac3de6` ↔ 收官头,CI 同款 sym 构建) | 0 增 / 0 删 / **3 认定 resize** / 0 重命名,`.text` −16 B:`SwapchainObject::Create`(ev 表面事件化)、`CopyTexSubImage2D`(hd 传输臂)、`ScopedRestartIndexSubstitution`(server-shadow 臂);pull 构建零 `MG_Remote` 符号 | +| G5(p3a / p4a 保护区) | 字节一致 | +| 生成器 / 卫生门 | 双生成器 --check/--self-test 绿;doc 引用、include 闭包、dirty-surface 绿 | +| Redmi 四臂复测 | **未做**(记录项,需设备窗口;barrier tax 重测随之) | +| 79 trace 普查 | **未重跑**(全集语料不在本机;P5b 的 72/6/1 仍以其头为准) | + +审计的 59 处直接访问的终态:纹理纹素 / 形状 / 脏区改读 server staged shadow(tx);反向通道四条事件(ev);句柄解析全部按记录(hd);`applier_reset` / `object_death` 上 wire(ct);值类残余读清零(rv);双层角色守卫 + `InBarrierWait`(gt)。**未到期的具名债**:`MGPipeReverseAnnouncementScope`(绑定记录 P4b 才发射的 ensure/通告族)与 `MGPipeFrontendKeyedRegistryScope`(G6 前端键 twin registry,P3b/P4b 重键)两个 scope 内的只读探测,以及对象类 15 行 BARRIER-PULLED——全部具名、可 grep、有退役阶段。 + +## 落地形状(原 `ARCHITECTURE.md` §17.6) + +### 17.6 `inproc` 仍经共享地址空间的访问,与 P5c 的形状(已落地,头 `b88e8487`) + +P5 / P5b 的 wire 只覆盖 verb 记录、`SEG_STAGE` blob、reply 与 caps 快照。对 `a79a0af6` 的只读静态审计(`ROADMAP.md` "P5c 计划",报告 `~/w7/notes/p5c/p5c-audit-v1.md`)列出 59 处仍靠 verb barrier 与同一地址空间才正确的直接访问,最重的四类:**纹理纹素**虽已过 `SEG_STAGE` 但 applier 丢掉指针、Espryt 回读 client 的 `MipmapStorage`(整个纹理家族不在 `FieldOwnership.def`,`rsp` / strict / audit 都看不见);**反向通道**是 apply 线程直接调进 client `MG_State`(`OnBufferWriteback` 传裸指针,`SEG_EVENT` 已铺好但零 producer,十个回调只装了两个);**server 用前端 `GetLifetimeId()` 去 client 的 slot 分配器查找甚至铸造句柄**,而记录里其实已带句柄;**Magma** 直接读 client 的 caps 镜像、直接 `MarkGpuWritten`、直接写 client 的 mip 存储。 + +P5c 的设计决定(六条全部落地,落地形状与偏差以 `MobileGL/MG_Remote/CONTRACT-P5C.md` 为最新权威):(1) **server 端纹理 staged shadow**——`ApplyTextureUpload` 采纳 `SEG_STAGE` 字节,`SyncMipmapsToBackend` 只读它与描述符,`0xDD` audit 因此覆盖纹理;(2) **`SEG_EVENT` 成为唯一反向通道**——`OnBufferWriteback` 的 `MGPBlobRef` 约定 `Seg = kSegEvent`,`OnGpuWritten` / `OnSurfaceChanged` 走同一 ring,Magma 与 Espryt 共用回调,溢出策略按 §11.7;(3) **sink 与 twin 按记录里的句柄解析**(`GetOrCreate(MGPipeHandle)`),server 永不触 `MGPipeSlots()`;(4) **两条控制记录** `applier_reset`(make-current 边)与 `object_death`(framebuffer 首次有 delete opcode),mailbox 只剩 EGL forwarder 给 P6;(5) **值类 BARRIER-PULLED 行改为每 verb 残余值记录或 server 自答**,对象类行保持 barrier 直到 twin 表落地(P3b/P4b、P7、P8);(6) **角色守卫**——split 构建有传输时,apply 线程触前端对象表面、GL 线程触 server 状态表面都是 `Fatal{RoleViolation}`;这是 P5c 的出口门,也是 P6 只做传输替换的前提。 + +落地时的三条结构性补充(契约 §3.1 与 §5.4 的具名豁免):审计漏了三族无句柄站点——绑定记录 P4b 才发射的 ensure/通告族(`MGPipeReverseAnnouncementScope`)、G6 前端键 twin registry(`MGPipeFrontendKeyedRegistryScope`,P3b/P4b 重键)、Magma 拆除期的隐藏资源;两个 scope 内的只读探测是具名、可 grep、带退役阶段的债,不是守卫的洞。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-p5c-audit.md`](BRIEF-p5c-audit.md) | Brief: p5c-audit — where the `inproc` split still reads the other role's memory | +| [`p5c-audit-v1.md`](p5c-audit-v1.md) | p5c-audit-v1 — where the `inproc` split still reads the other role's memory | diff --git a/docs/Disaggregated/P5D-INPROC-PERFORMANCE.md b/docs/Disaggregated/notes/p5d/P5D-INPROC-PERFORMANCE.md similarity index 100% rename from docs/Disaggregated/P5D-INPROC-PERFORMANCE.md rename to docs/Disaggregated/notes/p5d/P5D-INPROC-PERFORMANCE.md diff --git a/docs/Disaggregated/notes/p5d/README.md b/docs/Disaggregated/notes/p5d/README.md new file mode 100644 index 00000000..ab41a7e3 --- /dev/null +++ b/docs/Disaggregated/notes/p5d/README.md @@ -0,0 +1,54 @@ +# P5d — `inproc` 性能专项(`cb06538c`、`56a77348`、`1f8de61b`) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 起因:P5c 收官后 FCL + Minecraft 26.3(VD12)上 inproc 只有 7-13 fps,monolith ~116 fps。 +- 三轮:persistent-map 推送三级臂(mprotect 脏页 + 哈希抑制 + 绑定过滤)、barrier 批处理(`MOBILEGL_IPC_BATCH_WAITS`)与延期销毁、自旋时钟与零锁 apply 轮询、`OnApplyThread()` 内联、shadow 页对齐等。 +- 结果(Redmi,CPU 定频):inproc **64.3 → 103-106 fps**(p50),client 线程 CPU 15.0 → 9.2 ms/帧、apply 12.4 → 7.0-7.4。未完成:R-1 序列化(研究结论归 P11 的 `gPipeInputs` 版本化)、线程放置(内核忽略亲和)。 +- 报告:[`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md)(2026-09-24 从上层移入)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5d** `inproc` 性能专项 +- **状态**:✅ `cb06538c`、`56a77348`、`1f8de61b` +- **落地什么 / 范围**:一二轮:persistent-map 推送三级臂(mprotect 脏页追踪 + 哈希抑制 + 绑定过滤 `PushDrawConsumers`)、barrier 批处理 `MOBILEGL_IPC_BATCH_WAITS` + 延期销毁队列、自旋时钟 64 合 1、零页注册 + shadow 页对齐分配。三轮(按符号化 profile 逐项切,四包 + 一份 lockstep 可行性研究):apply 空转轮询零锁(控制邮箱原子影子位)、`Doorbell::Wait` 校准自旋稳态不读时钟、wait/park 计数进 stats;`OnApplyThread()` 内联为线程指针比较、三个 scope 深度计数器退掉 `thread_local`;shadow 分配尺寸补页、追踪范围向外对齐、边块哈希消失、脏页排空修复;image unit 高水位、残余填充 memo、server stamp 常量表;`generate_mipmap` 不再免等(apply 读残余输入)、death 测试围栏 +- **验收门 / 证据**:三轮设备(Redmi,CPU 定频):inproc **64.3 → 103-106 fps**(p50),client 线程 CPU 15.0 → 9.2 ms/帧、apply 12.4 → 7.0-7.4;monolith 同场景 115(120 Hz vsync 封顶的运行)/ 206(一次未封顶,渲染线程 5.0 ms/帧 大核);配对比较以逐线程 CPU/帧为主,见报告"数字"节;unit 2203/2203、`integration-split` 111/111(两条曾 flaky 的用例复跑 6/6);双生成器、include 闭包、dirty-surface、doc 引用绿。未完成:R-1 序列化(研究结论:留给 P3b/P4b 后的 P11 `gPipeInputs` 版本化)、线程放置(内核忽略亲和)、`SetHashSuppressor` 哈希等小项。报告:[`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md) + +## 实测:P5d(`cb06538c`、`56a77348`、`1f8de61b`;Redmi `2f7cbe2e`,FCL + Minecraft 26.3-rc-3 世界 "test",VD12,DirectGLES)(原 `MEASUREMENTS.md` §9) + +协议:CPU 定频(大核 1958400 / 小核 1555200,`pin_device.sh`)、风扇二档、世界内 20 s 静置后取 30 s 窗口;fps = `MGPipe stats:` 行的帧数 / 时间(应用侧 swap 计数),逐线程 CPU = `/proc//task/*/stat` 的 utime+stime 差 / 帧数。两个已知的不受控变量:GPU 由厂商守护进程在每次启动时把 `min_pwrlevel` 打回 12(inproc 各臂 GPU 都在 342 MHz 空转、CPU 受限,不受影响;monolith 或受 GPU 或 120 Hz vsync 封顶);内核对 app 线程忽略 `sched_setaffinity`,client GL 线程常落在中核(cpu2/4/5)、apply 线程常驻大核 cpu7。因此配对比较以 **client 线程 CPU ms/帧** 为主指标。 + +| 构建 | 臂 | fps p50 | client ms/帧(核) | apply ms/帧(核) | 备注 | +|---|---|---|---|---|---| +| 二轮头 `56a77348` | inproc | 64.3 | 15.03 (cpu5) | 12.42 (cpu7) | 起点;`SPIN_US=2000` 68-73(三次),`=10000` 63,`SERVER_AFFINITY=off` 65.9 | +| 二轮头 `56a77348` | monolith | 116.5 | 5.85 (cpu7) | — | 75% 忙,vsync 120 Hz 封顶 | +| 三轮审查前构建 | inproc | 114.0 | 8.68 (cpu4) | 6.93 (cpu7) | `SPIN_US=2000` 112.3(不再有收益) | +| 三轮审查前构建 | monolith | 115.7 | 5.70 (cpu5) | — | 封顶 | +| 三轮(本提交) | inproc | 103-106 | 9.2 | 7.0-7.4 | 交错会话,见下 | +| 三轮(本提交) | monolith | 115(封顶)/ 206(一次未封顶) | 5.0-6.2 (cpu7) | — | 三次:115.3 / 115.1(120 Hz 封顶)与 205.8(未封顶) | + +交错会话(三轮头,post-review 构建,同一次定频、同一天): + +| 臂(顺序) | fps p50 (min / max) | client ms/帧(核) | apply ms/帧(核) | 备注 | +|---|---|---|---|---| +| mono1 | 205.8 (110 / 280) | 5.0 (cpu7) | — | 未封顶的一次;窗口内 110→280 摆动 | +| inproc1 | 106.1 (91 / 112) | 9.24 (cpu4) | 7.0 (cpu7) | | +| mono2 | 115.3 (108 / 117) | 6.2 (cpu7) | — | 120 Hz 封顶 | +| inproc2 | 103.3 (82 / 111) | 9.2 (cpu5) | 7.4 (cpu7) | | +| inproc3 | 101.9 (83 / 109) | 9.1 (cpu5) | 7.3 (cpu7) | | +| mono3 | 115.1 (109 / 117) | 6.1 (cpu7) | — | 封顶 | + +审查前构建(同一天早些、机身 50.8 °C 起)inproc 114.0 / client 8.68 ms(cpu4)/ apply 6.93;post-review 构建 pmap 线流量与之逐字节相同(364.7 KB/帧),差在运行间漂移(机身 53-54 °C 起、中核放置)之内。 + +`wait[]` 计数(三轮加入 stats 行):审查前构建 inproc 30 s 窗口累计 srv=7.56M / srvpark=76k、cli=6.70M / clipark=17k;`SPIN_US=2000` 下 park 各降两个量级、fps 不变——park 已不是主项。持久映射线流量 pmap ≈ 0.36 MB/帧(二轮 0.23,三轮多了首推与脏页排空)。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P5D-R3.md`](BRIEF-P5D-R3.md) | P5d round 3 brief: inproc performance, the profile-directed cuts | +| [`P5D-INPROC-PERFORMANCE.md`](P5D-INPROC-PERFORMANCE.md) | P5d 报告:inproc 性能专项(2026-09-18) | +| [`RESULTS-P5D-R3.md`](RESULTS-P5D-R3.md) | P5d round 3 - device results log (Redmi 2f7cbe2e, FCL + MC 26.3-rc-3 world "test", VD12, DirectGLES) | +| [`reports/`](reports/) | 5 个文件:三轮各包(B / D / T …)与 lockstep 可行性研究的报告 | diff --git a/docs/Disaggregated/P5E-RUNAHEAD.md b/docs/Disaggregated/notes/p5e/P5E-RUNAHEAD.md similarity index 100% rename from docs/Disaggregated/P5E-RUNAHEAD.md rename to docs/Disaggregated/notes/p5e/P5E-RUNAHEAD.md diff --git a/docs/Disaggregated/notes/p5e/README.md b/docs/Disaggregated/notes/p5e/README.md new file mode 100644 index 00000000..6ec59cfa --- /dev/null +++ b/docs/Disaggregated/notes/p5e/README.md @@ -0,0 +1,193 @@ +# P5e — 退役 Espryt draw path 的 lockstep(run-ahead,2026-09-19 收官) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 一句话:client 发布完就走;server 用记录带的句柄解析每一个对象;它唯一可以读的 client 内存,是一条 client 确实被挡在后面(barriered)的记录的残余 fill。契约 `MobileGL/MG_Remote/CONTRACT-P5E.md`(规则 F),裁定 ID-80..136([`INTEGRATOR-DECISIONS-P5E.md`](INTEGRATOR-DECISIONS-P5E.md))。 +- 十二包三波:c0e → id;vi / sb / pg / tx2 / fb ∥ ra(+ fix1);pa / mv / gl / ra2。`kMGPipeP5eRunAheadReady` 与 `kMGPipeP5eClientWaitRuleLanded` 均已翻。 +- 门:unit strict 两臂 2256/2256;`integration-split` 179/179;`RUN_AHEAD=0` 对照 179/179;`VERB_BARRIER=0` 变红;`MOBILEGL_IPC_AUDIT=1` 179/179。 +- 设备:VD12 下 run-ahead 与 monolith 都撞 120 Hz 上限;**VD32(~3550 draws/帧)下 inproc 与 monolith 齐平**(59.9 vs 58.5 fps),run-ahead 相对 lockstep p50 +69%、client CPU −40%。 +- **具名未决**:E1 对照需按 ID-114 / ID-122 重新定义(`VERB_BARRIER=0` 会顺手关掉 run-ahead,所求的 `Fatal{BarrierViolation}` 出现 0 次)。 +- 报告:[`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md)(2026-09-24 从上层移入)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5e** 退役 Espryt draw path 的 lockstep +- **状态**:✅ **已收官(2026-09-19)**,附一条具名未决:E1 对照需按 ID-114 重新定义 +- **落地什么 / 范围**:契约 `MG_Remote/CONTRACT-P5E.md`(规则 F:**未设障**记录的 apply 不得读任何 client 内存;ID-84 限定它只约束未设障记录);裁定 **ID-80..136**(`~/w7/notes/p5e/INTEGRATOR-DECISIONS-P5E.md`)。**十二个包,三波**:wave 1 **c0e**(契约 + 线上行:`PipeCalls.def` 第五列 `WaitClass`、`set_program_bindings` opcode 80、`kCapRunAheadApply` 位 10 仅 DirectGLES 臂、`kDrawClientArrays`、子系统位 13、两个旋钮)→ **id**(registry 按 `{slot, gen}` 重键、by-handle resolver、分配器守卫);wave 2 **vi / sb / pg / tx2 / fb** 五个 per-draw 家族 ∥ **ra**(等待规则、present credit、`gPipeInputs` 变 server 角色内存),加 **fix1**(设备发现的 monolith 臂回归,ID-107);wave 3 **pa**(program 家族离开前端)、**mv**(multi-draw + 五个档位的门禁条目)、**gl**(门禁机制本身 + 两处潜伏崩溃)、**ra2**(翻开关后暴露的竞态)。**`kMGPipeP5eRunAheadReady` 与 `kMGPipeP5eClientWaitRuleLanded` 均已翻**。最大的那次迁移(逐 draw 的 `GetProgramForDraw`)**没有改动任何线上结构**:记录早就带着逐 link 的 `ProgramArchive`,缺的是仍去问前端对象的读者。报告 [`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md) +- **验收门 / 证据**:出口门已逐条跑过(BRIEF-P5E §3,2026-09-19 在集成树上,run-ahead 已武装):**item 1** unit 在 strict 两臂均 **2256/2256**;**item 2/3** `integration-split` **179/179**、逐条普查零 `Fatal{`、`Admitted{` 恰为八行;**item 4(a)** `RUN_AHEAD=0` 作为配对对照 **179/179 绿**;**item 4(b)** `VERB_BARRIER=0` **变红**(车道 62/179);**item 4(c)** Magma 逐字记下 `run-ahead requested, server does not publish kCapRunAheadApply - running lockstep`,而 Espryt 对照记下 `run-ahead ARMED`;**item 5** 在集成树上重跑 red-once(ID-121 重排后的形式):倒掉 pa 的臂得 `GetProgramForDraw@DrawArrays`、倒掉 mv 的臂得 `GetBoundVertexArray@DrawArrays`,**恰好各一个具名对**,还原后各自转绿;**item 7** `MOBILEGL_IPC_AUDIT=1` **179/179**。**唯一未过的是 E1 对照脚本本身**(ID-122):已修掉它两个遮蔽性缺陷(选中了一个在本车道永远跑不起来的阳性对照用例;ctest 与日志助手对“谁被选中”意见不一致),但它索要的 `Fatal{BarrierViolation}` 在整次运行里 **出现 0 次**:`BATCH_WAITS` 默认值 1 下那个重叠不发生,而 run-ahead 武装后 `VERB_BARRIER=0` 还会顺手关掉 run-ahead(ID-114),它产生的那条臂**既不是 lockstep 也不是 run-ahead**。E1 需要重新定义它要证伪什么,不是再调阈值;在想清楚之前不动它的计数规则,否则只是换一种伪绿。G1 仍由 CI 断言(ID-123) + +## 实测:P5e(wave 1 `f6cfcbd3` → wave 2 `44f91c74` → fix1 `66621767` → ID-110 `3cc4e1ec`)(原 `MEASUREMENTS.md` §10) + +P5e 尚未收官(`kMGPipeP5eRunAheadReady` 仍为 false),本节记的是**设备验证一轮**,问题是"八包合并后两条传输臂还能不能正常跑游戏",不是配对性能。 + +### 主机门(WSL `~/w7/p5e-int`,split flavour,头 `3cc4e1ec`) + +| 门 | `44f91c74` | `3cc4e1ec` | +|---|---|---| +| unit | 2250/2250 | **2250/2250** | +| `integration-split` | 113/113 | **116/116** | +| `integration-gpu`(两条运行时臂,ID-109 起为常设步) | **1157/1294**(137 SEGFAULT / 38 scenario) | **1294/1294** | +| `integration-split-strict` | 7/116 | 7/116(预期红) | + +`integration-gpu` 那一列是本阶段最重要的一个数:`44f91c74` 的 137 个 SEGFAULT 在当时**没有任何门禁步骤会看到**——`integration-split` 的每一条都导出 `MOBILEGL_TRANSPORT=inproc`,而 push 构建有两条服务端臂。详见 `CURRENT_STAGE_PROGRESS.md` §2.5。 + +### 设备(Redmi `2f7cbe2e`,FCL fordebug + Minecraft 26.3-rc-3 世界 "test",VD12,DirectGLES;进世界后 60 s 采样) + +| 臂 | 进世界 | fps | draws/帧 | 进程 | Fatal / crash buffer | +|---|---|---|---|---|---| +| monolith(`44f91c74`,修复前) | — | — | — | **65 s 后进程消失** | SIGSEGV,栈见 §2.5 | +| monolith(`3cc4e1ec`) | 31 s | 263.5 | 845 | 存活 | 无 | +| inproc(`3cc4e1ec`) | 31 s | 136.1 | 853 | 存活 | 无 | +| inproc 复跑(`3cc4e1ec`) | 32 s | 140.3 | 852 | 存活 | 无 | + +monolith 这一次未被 120 Hz vsync 封顶(与 §9 的 205.8 那次同类),两臂 fps 不可直接相比。inproc 的 136-140 与修复前记录的 140.0 / 144.9 同档:fix1 改的那行两臂都读,split 臂没有被拖慢。 + +### 测量纪律(本轮与上一轮踩到的坑,全部是污染而非代码问题) + +- `adb install` 的收尾会在一两分钟后杀掉正在跑的同包进程(`am_kill … due to installPackageLI`):装包与测量之间必须等到 `dex2oat` 退出。 +- 派给外部 CLI 的任务,其进程会活过 harness 的完成通知;**派出后不得自己再跑同一件事**,否则两套实验同时对一台手机 force-stop / 清 logcat,得出假失败。 +- 不得在脚本正被 bash 执行时修改它(边读边执行);要改先冻结副本。 +- 游戏内 F3 的 `GIT@` 戳记在增量构建下是旧的,**判断库版本要看 APK 里 `.so` 的符号 / 字符串**,不看戳记。 + +## 实测:P5e wave 3 — run-ahead 武装后的设备矩阵(头 `25fba0d5`)(原 `MEASUREMENTS.md` §11) + +Redmi `2f7cbe2e`,FCL fordebug + Minecraft 26.3-rc-3 世界 `test`,VD12,DirectGLES,~850 draws/帧。 +**CPU 定频**(见下),GPU 钉 pwrlevel 0 且每臂重新断言,风扇开,进世界后 20 s 稳定再取 30 s 窗口,臂交错。 + +### 定频,以及为什么这一节必须先讲它 + +第一轮矩阵没有定频,于是 **monolith 自己那一臂**的逐帧 CPU 在两次运行之间从 4.43 ms 跳到 7.07 ms, +而慢的那次**温度更低**(53.5 °C vs 58.9 °C)——不是热降频,是 walt governor 把 policy0 从 2745 MHz +拉到 748 MHz。未定频的逐线程 CPU ms/帧**不能跨臂比较**,那一轮矩阵作废。 + +`tools/device_bench/pin_device.sh` 不认识本机 serial,并**明确拒绝猜**("pin path 因 SoC 而异,猜错会静默失败")—— +这是对的,也正是它没有静默半应用的原因。按本机实测节点另写 `pin_redmi.sh`:写序 min→硬件底、max→目标、 +min→目标(顺序要紧,否则按 stock 相对目标的位置会半应用),并**回读 `scaling_cur_freq` 确认**,跑完再 check 一次。 + +### 结果(每臂两次,离散度 < 2%) + +定频 little 1555200 / big 1958400: + +| 臂 | fps p50 | fps max | client ms/帧 | apply ms/帧 | +|---|---|---|---|---| +| monolith | 115.1 | 117.0 | 6.19 | — | +| inproc run-ahead | 117.6 / 117.9 | 118.3 / 118.6 | 6.71 / 6.63 | 4.30 / 4.32 | +| inproc lockstep (`MOBILEGL_IPC_RUN_AHEAD=0`) | 111.1 / 110.7 | 114.9 / 115.1 | 8.27 / 8.09 | 6.44 / 6.41 | + +定频 little 1996800 / big 1958400(更高会被厂商限幅器夹住): + +| 臂 | fps p50 | fps max | client ms/帧 | apply ms/帧 | +|---|---|---|---|---| +| monolith | 115.6 / 115.4 | 117.2 / 117.5 | 6.45 / 6.25 | — | +| inproc run-ahead | 117.6 / 118.0 | 118.5 / 118.6 | 6.92 / 6.87 | 4.43 / 4.35 | +| inproc lockstep | 111.6 | 113.7 | 8.16 | 6.53 | + +### 读法 + +- **配对 A/B**:`MOBILEGL_IPC_RUN_AHEAD=0` 与默认是同一构建、同一份记录、同一定频,唯一差别是客户端等不等 + (ID-114:**不得**用 `VERB_BARRIER=0`,它是 `RunAheadArmed()` 的第一个合取项,会顺手关掉 run-ahead, + 而且实测几秒内即 `Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@ClientWaitSync"}`)。 + 差值:client **−18%**、apply **−33%**、fps p50 **+6%**。 +- **两个定频档下 monolith 与 run-ahead 都撞到 120 Hz 面板上限**(max 117-118.6),**只有 lockstep 撞不到** + (max 113.7-115.1)。所以"inproc 是否追平 monolith"在本机真实负载上的答案是**追平了**;阶段开始时是 + **0.62-0.74 倍**(见 §10)。 +- 逐帧 client CPU:run-ahead 6.9 / monolith 6.3,多约 **8%**,两者都在上限之下有余量。 +- **未回答**:面板上限之上谁更快。本机定不住更高频率;一次未定频的高频窗口给出 monolith p50 201 / + run-ahead p50 181(约 1.10 倍),但那次不可配对,只作方向性提示。需要关 vsync 或换上限更高的设备。 + +### 侧写(`25fba0d5` 之前,`7c6f6886`,说明 run-ahead 为什么值得做) + +symbolized `simpleperf cpu-cycles`,客户端 GL 线程:`SessionProducer::WaitForAppliedOrEventBacklog` +**31.58% 自身时间**,其后第二名只有 3.72%。设备计数器同期显示客户端每帧进入 doorbell 等待约 **916 次** +(对 ~849 次 draw),其中 99.8% 靠自旋解决——`MOBILEGL_IPC_SPIN_US=0` 让每次等待都 park,帧率塌到 20.9。 +所以代价是**会合本身**,不是自旋参数,这也是为什么修法是"不再每次 draw 会合"而不是"把每次会合做便宜"。 + +### 11.1 渲染距离 32 的对照(同头 `25fba0d5`,~3550 draws/帧) + +§11 的矩阵在 VD12 下跑,而两条臂都撞到了 120 Hz 面板上限,所以那一节回答的是"能不能撞到上限", +不是"谁更快"。把渲染距离拉到 **32**(`options.txt` 的 `renderDistance`,备份为 `.p5e-backup`,测完已还原) +负载涨到 **~3550 draws/帧**(VD12 的 4.2 倍),**两条臂都远离任何上限**(游戏自身 `enableVsync:false`、 +`maxFps:260`,实测 max 只有 37-71),这才是可比的一轮。 + +条件:CPU 定频 little 1555200 / big 1958400(跑前跑后各 check 一次,均 PINNED),GPU 钉 pwrlevel 0, +风扇开,进世界后**静置 75-150 s**再取 40 s 窗口——VD32 的区块流式加载远比 VD12 长,沿用 20 s 静置会把 +加载期算进稳态。臂交错。 + +| 臂 | n | fps p50 中位数 | p50 范围 | client ms/帧 中位数 | apply ms/帧 | +|---|---|---|---|---|---| +| monolith | 5 | **58.5** | 56.3 – 67.9 | 16.19 | — | +| inproc **run-ahead** | 5 | **59.9** | 56.8 – 61.1 | **15.72** | 11.1 – 12.1 | +| inproc lockstep(`RUN_AHEAD=0`) | 2 | 35.5 | 34.5 – 36.5 | 26.17 | 19.7 – 20.5 | + +**结论:在 VD32 下 inproc 与 monolith 齐平**,中位数上 run-ahead 甚至略微领先(59.9 vs 58.5, +client CPU 15.72 vs 16.19),差值远在 monolith 自身的离散度之内——**monolith 五次的范围是 56.3-67.9 +(±10%),run-ahead 是 56.8-61.1(±4%)**,也就是说 split 臂不但追平,还更稳。`mono1` 的 67.9 是全场唯一 +的离群值(装包后第一次运行)。 + +**run-ahead 相对它取代的 lockstep:p50 +69%、client CPU −40%、apply CPU −43%。** +负载越重它越值钱,这是预期内的——会合次数随 draw 数走,VD12 是每帧约 900 次,VD32 约 3600 次。 + +两条臂**都是 client 线程打满**(run-ahead 15.72 ms CPU / 16.7 ms 帧 ≈ 94%;monolith 同理),所以这一列 +是各自真正的瓶颈,可以直接比。 + +**下一个机会,不是缺陷**:apply 线程只有 11.1-12.1 ms,client 有 15.7 ms——**两侧不平衡**。把工作从 client +挪到 apply 会直接降低瓶颈。这在 lockstep 下毫无意义(两侧串行相加),run-ahead 之后才成立,是本阶段 +解锁出来的新优化方向,留给后续阶段。 + +## 落地形状(原 `ARCHITECTURE.md` §17.7) + +### 17.7 P5e:client 在 Espryt draw 路径上跑在 apply 前面(权威为 `MobileGL/MG_Remote/CONTRACT-P5E.md`) + +P5d 把 `inproc` 变成 client 线程 CPU-bound,帧仍是 `client 工作 +(每 draw)等 apply + 交接`。那个等待存在,是因为 draw 的 apply 仍在解引用 client 拥有的内存:`FieldOwnership.def` 的对象类 BARRIER-PULLED 行,以及两个 P5C 具名豁免 scope 里的前端键 twin registry。**P5e 的一句话:client 发布完就走;server 用记录带的句柄解析每一个对象;它唯一可以读的 client 内存,是一条 client 确实被挡在后面的记录的残余 fill。** + +- **barriered 谓词**(§2.1,裁定 3 / ID-83):`MGPipeBarriered(op, payload, applierState)` = 静态 `WaitClass` 列 ≠ `kWaitNone`,或 XFB span 打开时的 `kCtxVerb`,或带 `kDrawClientArrays` 的 draw。两侧算同一个函数、同一份数据——client 在 emit 时用 `ctx.IsTransformFeedbackActive()`,server 用应用过的 `MGPContextValues` 镜像,而 `set_context_values` 在 ring 上先于 verb,所以两者一致。没有第四条升级;加一条是整合者的裁定加契约里的一行。 +- **规则 F**(§0):**unbarriered 记录的 apply 不读任何 client 内存**——不解引用前端对象、不读 BARRIER-PULLED 行、不探测也不铸造 `MGPipeSlots()`、不持有前端对象的 `SharedPtr`。违反是具名 Fatal,**与 `MOBILEGL_IPC_STRICT_ERRORS` 无关**:那个值按构造就是撕裂或陈旧的,没有"计一笔"的臂。barriered 记录原样保留 P5C 的语义(裁定 4 / ID-84),这正是第一次落地能做小的原因:回读、CopyTex、`GetTexImage`、`set_storage_block_binding`、XFB 都留在 barriered 且拖后。 +- **`gPipeInputs` 变成 server 角色内存**(§3):unbarriered verb 的 validate 只跑第 2 步(tracker 走查)和第 3 步(发射器)——它们产出记录,这正是 unbarriered verb 被允许交给 server 的全部——序号自增、verb 戳、撤回 server 戳与整个第 4 步都不跑。`MGPipeInputUnfreshRead` 的计数器在 unbarriered 记录下变成**无条件中止**,这是把 `integration-split-strict` 从"预期红"变成硬绿门的那一处(§7)。 +- **run-ahead 的臂**:`RunAheadArmed()` = `m_barrierArmed && Ipc.RunAhead && Caps().HasCap(kCapRunAheadApply)`,在 `Start` 之后第一次 caps 采纳时**锁存**,此后只能被关掉、不能被打开——已经不等就发布出去的记录收不回来。Magma 的 `CallMask` 永远不带 bit 10(`MGPipeRunAheadCapBitsFor` 是纯函数,`MagmaPipeIdentityTest` 钉住),Espryt 则由 `MG_Backend/Init.cpp` 的 `kMGPipeP5eRunAheadReady` 一个常量把整相位关在惰性里,直到整合提交把它翻过来。 +- **对照臂**:`MOBILEGL_IPC_RUN_AHEAD=0` 是 A/B 的**对照**而不是负控制——server 代码、fill 决定、每一条记录两臂都一样,唯一的差别是 client 等不等;`MOBILEGL_IPC_VERB_BARRIER=0` 保留它自己的意思,并且因为它是那个合取式的第一项,它会直接把 run-ahead 关掉。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`BRIEF-P5E.md`](BRIEF-P5E.md) | BRIEF-P5E — the package plan for retiring the lockstep on the Espryt draw path | +| [`CONTRACT-P5E-draft.md`](CONTRACT-P5E-draft.md) | CONTRACT-P5E (draft) — the client runs ahead of apply on the Espryt draw path | +| [`FACTS-P5E.md`](FACTS-P5E.md) | P5e — the authoritative fact sheet for the doc update | +| [`FLIP-CHECKLIST.md`](FLIP-CHECKLIST.md) | P5e wave 3 — integration and the flip, as an executable checklist | +| [`INTEGRATOR-DECISIONS-P5E.md`](INTEGRATOR-DECISIONS-P5E.md) | INTEGRATOR-DECISIONS-P5E (2026-09-18, base `feat/disaggregated @ 2fde7034`) | +| [`KIMI-AUDIT-P5E.md`](KIMI-AUDIT-P5E.md) | Read-only audit brief (Kimi): every apply-thread read of client-owned memory on the DirectGLES inproc draw path | +| [`kimi-audit.md`](kimi-audit.md) | P5e audit — apply-thread reads of client-owned memory on the DirectGLES inproc draw path | +| [`KIMI-DEVICE-VERIFY.md`](KIMI-DEVICE-VERIFY.md) | Device verification brief (Kimi): build FCL with the P5e MobileGL and prove it still runs | +| [`P5E-RUNAHEAD.md`](P5E-RUNAHEAD.md) | P5e:退役 Espryt draw path 的 lockstep(run-ahead) | +| [`PACKAGE-PREAMBLE-P5E.md`](PACKAGE-PREAMBLE-P5E.md) | P5e package preamble (read first; applies to every package agent) | +| [`PERF-BASELINE-7c6f6886.md`](PERF-BASELINE-7c6f6886.md) | inproc vs monolith at `7c6f6886` — measured, and why | +| [`PERF-CORRECTION.md`](PERF-CORRECTION.md) | CORRECTION to `PERF-PROFILE-7c6f6886.md` §5 — run-ahead removes much less of the fill than §4 implied | +| [`PERF-NEXT-TARGETS.md`](PERF-NEXT-TARGETS.md) | After run-ahead: there is no second lever | +| [`PERF-PROFILE-7c6f6886.md`](PERF-PROFILE-7c6f6886.md) | The symbolized profile, and what run-ahead actually removes | +| [`PREAMBLE-ADDENDUM-WAVE3.md`](PREAMBLE-ADDENDUM-WAVE3.md) | Addendum to `PACKAGE-PREAMBLE-P5E.md` for wave 3 (pa / mv / gl), 2026-09-18 | +| [`SCOUT-P5E.md`](SCOUT-P5E.md) | P5e scouting brief: retire the lockstep on the Espryt draw path (client runs ahead of apply) | +| [`scout-S1.md`](scout-S1.md) | S1 — VAO / vertex input / index, under run-ahead | +| [`scout-S2.md`](scout-S2.md) | S2 — textures and samplers under run-ahead (P5e scouting report) | +| [`scout-S3.md`](scout-S3.md) | S3 - framebuffers, attachments, images (P5e scouting, head `2fde7034`) | +| [`scout-S4.md`](scout-S4.md) | S4 - programs / shader state (E note a.2) at head `2fde7034` | +| [`scout-S5.md`](scout-S5.md) | S5 — buffer binding points and the remaining rows (P5e scouting report) | +| [`scout-S6.md`](scout-S6.md) | S6 - transport mechanics of run-ahead (head `2fde7034`, paths under `MobileGL/`) | +| [`scout-S7.md`](scout-S7.md) | S7 - identity: the five twin registries and the allocator (head `2fde7034`) | +| [`STRICT-BASELINE-2fde7034.md`](STRICT-BASELINE-2fde7034.md) | Strict-lane baseline at `2fde7034` (the P5e starting point) | +| [`STRICT-WORKLIST.md`](STRICT-WORKLIST.md) | P5e — what is actually left before the strict lane can be a hard gate | +| [`TASK-c0e.md`](TASK-c0e.md) | Package c0e — the contract and the wire (P5e; lands first, inert) | +| [`TASK-fb.md`](TASK-fb.md) | Package fb — framebuffers, attachments, images, blit from records (P5e wave 2) | +| [`TASK-gl.md`](TASK-gl.md) | TASK-gl — make the strict lane mean something, and make the flip survivable | +| [`TASK-id.md`](TASK-id.md) | Package id — identity: handle-keyed twins, the guard, the scopes (P5e; lands second) | +| [`TASK-mv.md`](TASK-mv.md) | TASK-mv — the multi-draw path stops reading the frontend VAO | +| [`TASK-pa.md`](TASK-pa.md) | TASK-pa — the program family leaves the frontend on the draw and dispatch paths | +| [`TASK-pg.md`](TASK-pg.md) | Package pg — programs: the twin and every reflection answer come from records (P5e wave 2) | +| [`TASK-ra.md`](TASK-ra.md) | Package ra — the wait rule, present credit, server-owned `gPipeInputs` (P5e; parallel, lands last) | +| [`TASK-ra2.md`](TASK-ra2.md) | TASK-ra2 — make run-ahead actually correct | +| [`TASK-sb.md`](TASK-sb.md) | Package sb — `set_shader_buffers`: the binding-point tables cross the wire (P5e wave 2) | +| [`TASK-tx2.md`](TASK-tx2.md) | Package tx2 — textures and samplers by handle and window (P5e wave 2) | +| [`TASK-vi.md`](TASK-vi.md) | Package vi — VAO / vertex input / index from records (P5e wave 2) | +| [`WAVE3-CENSUS.md`](WAVE3-CENSUS.md) | Wave 3 census — the strict lane at `3c13fa4b` (pa + mv + ID-124 merged) | +| [`reports/`](reports/) | 14 个文件:各包报告与设备验证(`device-verify-3cc4e1ec.md` 等) | diff --git a/docs/Disaggregated/P5F-WIRE-COMPLETENESS.md b/docs/Disaggregated/notes/p5f/P5F-WIRE-COMPLETENESS.md similarity index 97% rename from docs/Disaggregated/P5F-WIRE-COMPLETENESS.md rename to docs/Disaggregated/notes/p5f/P5F-WIRE-COMPLETENESS.md index d4f2ea3e..0d704da4 100644 --- a/docs/Disaggregated/P5F-WIRE-COMPLETENESS.md +++ b/docs/Disaggregated/notes/p5f/P5F-WIRE-COMPLETENESS.md @@ -2,8 +2,8 @@ > **已收官(2026-09-20)**:f0 / f1 / fc / fe / fm / fs / fr / fv、字段归属收口、 > 主机出口门、Redmi 双后端六个 clean-boot 臂与跨模型族终审均完成。 -> 最终行为验收头 `cfca93c7`;[`收口报告`](notes/p5f/close-report.md) 为当前结论, -> [`交接`](notes/p5f/HANDOFF-P5F.md) 为下一阶段入口。P6 的 a6 / c6 / spawn 尚未实施。 +> 最终行为验收头 `cfca93c7`;[`收口报告`](close-report.md) 为当前结论, +> [`交接`](HANDOFF-P5F.md) 为下一阶段入口。P6 的 a6 / c6 / spawn 尚未实施。 > 当前字段:41 RECORD_SUPPLIED / 6 APPLIER_DERIVED / **0 BARRIER_PULLED** / 16 FATAL; > 两份 marker 棘轮为空,双后端逐帧 `rsp=0`。旧指针 getter 保持 FATAL,不以改名消债。 > @@ -194,8 +194,8 @@ Magma 今天不发布 `kCapRunAheadApply`,所以它跑 lockstep——**lockste 跨进程后函数指针与栈地址都无意义。 > **fc 已落地**:行号以基线 `7f1d2134` 为准(本节是"在基线头上实测"的清单);落地后的形态、 -> 逐 op 映射与契约草稿见 [`notes/p5f/fc-report.md`](notes/p5f/fc-report.md) 与 -> [`notes/p5f/fc-contract-draft.md`](notes/p5f/fc-contract-draft.md)。 +> 逐 op 映射与契约草稿见 [`notes/p5f/fc-report.md`](fc-report.md) 与 +> [`notes/p5f/fc-contract-draft.md`](fc-contract-draft.md)。 ### 2.4 进程级静态,语义却是 per-context @@ -273,7 +273,7 @@ f0 普查(已集成):§2.4 的完整静态清单 + §2.6 的逐站点清 ## 6 出口门 -**七项均已完成**,数字与原始证据路径见 [close-report.md](notes/p5f/close-report.md)。 +**七项均已完成**,数字与原始证据路径见 [close-report.md](close-report.md)。 双块 212 项为 206 PASS + 6 个按名称和原因锁定的既有 skip;RSP 独立门必须为 双后端 2 PASS / 0 skip。缺失、重复、未运行的 JUnit 条目或额外 skip 均不能过门。 diff --git a/docs/Disaggregated/notes/p5f/README.md b/docs/Disaggregated/notes/p5f/README.md new file mode 100644 index 00000000..9589c56b --- /dev/null +++ b/docs/Disaggregated/notes/p5f/README.md @@ -0,0 +1,61 @@ +# P5f — 一切状态上 wire(2026-09-20 收官) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 目标:两个角色之间除 `SEG_CMD` / `SEG_STAGE` / `SEG_REPLY` / `SEG_EVENT` / caps 快照 / 控制帧之外零交换,判据由人读审计改为**机器**(双块演练:`inproc` 下两个角色各自一份 `PipeInputs`)。计划与设计见 [`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md)(2026-09-24 从上层移入)。 +- 包:f0 普查、f1 双块、fc 控制帧、fe Espryt 字段、fm Magma record consumers、fs 静态世代 / 角色 / liveness、fr registry、fv 反向回调。 +- 结果:字段归属 **41 RECORD_SUPPLIED / 6 APPLIER_DERIVED / 0 BARRIER_PULLED / 16 FATAL**;strict / dualblock marker 表均空;双后端逐帧 `rsp=0`;unit 2310、GPU 1377 零失败;G1 恒等;Redmi 六个 clean-boot 臂 60 PASS / 6 skip / 0 failed。验收代码 WSL `cfca93c7`(Windows `c42577a4`)。收口 [`close-report.md`](close-report.md)、审查 [`close-review.md`](close-review.md)、设备 [`device-report.md`](device-report.md)。 +- 之后同目录还记了两件事:**Magma run-ahead**(`194382c9`,[`magma-runahead.md`](magma-runahead.md))与 Magma inproc 实际游戏修复 / RD32 性能修复;以及 **CI / inproc 功能补齐**(2026-09-21 收官,`e1bf3677`,交接 [`HANDOFF-CI-INPROC-20260920.md`](HANDOFF-CI-INPROC-20260920.md))。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P5f** 一切状态上 wire +- **状态**:✅ **已收官(2026-09-20)**;验收代码 Windows `c42577a4` / WSL `cfca93c7885f` +- **落地什么 / 范围**:f0 普查、f1 双块、fc 控制帧、fe Espryt 字段、fm Magma record consumers、fs 静态世代/角色及 liveness、fr registry、fv 反向回调均已合并。字段归属 **41 RECORD_SUPPLIED / 6 APPLIER_DERIVED / 0 BARRIER_PULLED / 16 FATAL**;strict / dualblock 预期 marker 集合均空。控制帧与静态量工作已完成,不再列为 P6 待实现项 +- **验收门 / 证据**:unit **2310 = 2300 PASS + 10 skip**;GPU **1377** 零失败;ordinary / strict split **180 = 176 PASS + 4 skip**;双块 **212 = 206 PASS + 6 精确具名 skip**;双后端 RSP 门 **2 PASS / 0 skip**、逐帧 rsp=0。G1 `.text` 10806051、symbols 27815 恒等且 0/0/0/0;G2 3016 同名;G14 3646→3744 无删名。逐包与收官修复均有 red-once;Claude 异族终审 P1/P2 已修;Redmi 六 clean-boot 臂 **60 PASS / 6 skip / 0 failed**、36 个窗口 rsp=0。详见 [`close-report`](close-report.md) + +## 状态注记(原 `ROADMAP.md` 页首) + +> **P5f 后续 Magma run-ahead 已完成**(`194382c9`,2026-09-20):主机真实排队、credit、负控及 Vulkan 同步验证通过,Redmi 同包 Magma 开/关与 GLES 三臂 FCL 世界实测通过。见 [报告](magma-runahead.md)。P6 / P7 全阶段状态不因此改变。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P5f 出口(2026-09-20)**:双块和 strict 空棘轮、逐帧 rsp=0、零 BARRIER_PULLED、主机全门、异模型族审查修复和 Redmi 六 clean-boot 臂全部完成;P6 前提解除,实施未开始。 +- **CI / inproc 收尾出口(2026-09-21)**:`feat/disaggregated` 主产物拆分编译 + inproc 主验收全绿——unit 2328 零失败、`integration-gpu` 双 transport 各 1455 零失败、retrace 77 例 monolith 77/77 且 inproc 的唯一间歇项(`bsl-esc-menu-854` DirectVulkan,lavapipe 固有 texture-handle 注册停顿越过旧 30 s barrier 预算)在预算修正为 120 s 后隔离 10/10 绿、18 项 CI 门全绿、双 ABI APK 与 AVD 生命周期验证通过;合入头 `e1bf3677`,最终矩阵以远端 CI 为准。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`close-report.md`](close-report.md) | P5f 收口报告 | +| [`close-review.md`](close-review.md) | P5f 阶段末跨模型族审查 | +| [`device-report.md`](device-report.md) | P5f Redmi 设备门 | +| [`f0-dualblock-recon.md`](f0-dualblock-recon.md) | P5f / f0 — f1 双块机制设计侦察(只读普查) | +| [`f0-egl.md`](f0-egl.md) | f0-egl — P5f §2.3 EGL / surface 控制面普查(只读) | +| [`f0-env.md`](f0-env.md) | P5f / f0 — 构建与门禁机制侦察(实现代理 cheatsheet) | +| [`f0-fields.md`](f0-fields.md) | f0 普查 · §2.1 BARRIER_PULLED 15 字段逐字段载体判定 | +| [`f0-magma.md`](f0-magma.md) | f0 普查 · §2.2 Magma(DirectVulkan)跨角色读写 | +| [`f0-registry.md`](f0-registry.md) | f0 普查子项:§2.5 registry 重键普查(`MGPipeFrontendKeyedRegistryScope`) | +| [`f0-reverse-channel.md`](f0-reverse-channel.md) | f0-reverse-channel — P5f §2.6 反向通道逐站点普查(只读) | +| [`f0-statics.md`](f0-statics.md) | P5f f0 普查(只读):进程级静态量中语义属于 per-context / per-session 的成员 | +| [`f1-report.md`](f1-report.md) | P5f / f1 — 双块演练机制:落地报告 | +| [`fc-contract-draft.md`](fc-contract-draft.md) | fc — 控制面帧契约草稿(P5f 包 fc;CONTRACT-P5F.md 本体留给集成收口) | +| [`fc-report.md`](fc-report.md) | P5f / fc — EGL/surface 控制面帧化:落地报告 | +| [`fcl-e2e-report.md`](fcl-e2e-report.md) | P5f FCL / Minecraft 端到端补测 | +| [`fe-report.md`](fe-report.md) | P5f / fe — Espryt 字段退役报告 | +| [`fm-report.md`](fm-report.md) | P5f fm — Magma 跨角色读写退役 | +| [`fr-report.md`](fr-report.md) | P5f / fr — registry 身份边收口 | +| [`fs-report.md`](fs-report.md) | P5f fs — 静态状态世代与 server liveness | +| [`fv-report.md`](fv-report.md) | P5f fv — 反向通道归属与遗留对象边界 | +| [`HANDOFF-CI-INPROC-20260920.md`](HANDOFF-CI-INPROC-20260920.md) | CI / inproc 功能补齐交接(2026-09-20,用户要求停止) | +| [`HANDOFF-P5F.md`](HANDOFF-P5F.md) | P5f 交接:已全部收官 | +| [`magma-inproc-fix.md`](magma-inproc-fix.md) | Magma inproc 实际游戏修复 | +| [`magma-rd32-performance-fix.json`](magma-rd32-performance-fix.json) | 脚本 / 数据 | +| [`magma-rd32-performance-fix.md`](magma-rd32-performance-fix.md) | Magma RD32 inproc 性能修复 | +| [`magma-runahead.md`](magma-runahead.md) | Magma run-ahead | +| [`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md) | P5f — 一切状态上 wire:跨进程前的最后一次归零 | +| [`rd32-fourway.json`](rd32-fourway.json) | 脚本 / 数据 | +| [`rd32-fourway.md`](rd32-fourway.md) | Redmi RD32 四组合性能记录 | +| [`wave2-report.md`](wave2-report.md) | P5f 波次 2 集成报告 | diff --git a/docs/Disaggregated/P6-CONTRACT-DRAFT.md b/docs/Disaggregated/notes/p6/P6-CONTRACT-DRAFT.md similarity index 98% rename from docs/Disaggregated/P6-CONTRACT-DRAFT.md rename to docs/Disaggregated/notes/p6/P6-CONTRACT-DRAFT.md index 54bf03b5..dca6126f 100644 --- a/docs/Disaggregated/P6-CONTRACT-DRAFT.md +++ b/docs/Disaggregated/notes/p6/P6-CONTRACT-DRAFT.md @@ -3,8 +3,8 @@ > **P5f prerequisites completed** (2026-09-20): field ownership has zero > `BARRIER_PULLED` rows; the dual-block and strict gates, context/lifetime work, > registry and reverse-channel guards, closing review fixes, and final Redmi -> paired runs have passed. See [`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md), -> the [closing review](notes/p5f/close-review.md) and [device report](notes/p5f/device-report.md). +> paired runs have passed. See [`P5F-WIRE-COMPLETENESS.md`](../p5f/P5F-WIRE-COMPLETENESS.md), +> the [closing review](../p5f/close-review.md) and [device report](../p5f/device-report.md). > > **P6 has not started:** `a6`, `c6`, and spawn implementation remain unstarted. > This remains a draft written before its own audit. Package `a6` ([`P6-SPAWN-PLAN.md`](P6-SPAWN-PLAN.md) §5) @@ -135,7 +135,7 @@ other gate here. **Implemented by P5f `fs`; P6 inherits and verifies it.** The statics recorded as P6 debt in `CONTRACT-P5C.md:538-539` no longer carry unqualified synced state between contexts. The -[fs report](notes/p5f/fs-report.md) records the full census, lifecycle rules and red-once evidence. +[fs report](../p5f/fs-report.md) records the full census, lifecycle rules and red-once evidence. - `ContextEpoch` combines native ES context generation, served-context serial and backend execution arm (monolith / transport server). `ScopedDefaultUnpackState` republishes defaults diff --git a/docs/Disaggregated/P6-ENDSTATE-REVIEW.md b/docs/Disaggregated/notes/p6/P6-ENDSTATE-REVIEW.md similarity index 99% rename from docs/Disaggregated/P6-ENDSTATE-REVIEW.md rename to docs/Disaggregated/notes/p6/P6-ENDSTATE-REVIEW.md index dbf2e7a9..b573159d 100644 --- a/docs/Disaggregated/P6-ENDSTATE-REVIEW.md +++ b/docs/Disaggregated/notes/p6/P6-ENDSTATE-REVIEW.md @@ -11,7 +11,7 @@ > **2026-09-21 更正(a6 收官后)**:a6 逐条复核了本文,推翻四处——`MG_State` 八站点的形状(§2.5)、 > abort 计数 93→92、`m_windowHandle` 的注入点形状、以及 `MGPipeSplitActive()` 的命名冲突。 > 正文已就地更正。a6 另发现四处本文未及的阻塞项(apply 线程在第一条记录前 abort、控制面两端都没接、 -> 控制回复等待无期限、`SurfaceReply` 装不下 result)。**以 [`notes/p6/a6-audit-v1.md`](notes/p6/a6-audit-v1.md) 为准。** +> 控制回复等待无期限、`SurfaceReply` 装不下 result)。**以 [`notes/p6/a6-audit-v1.md`](a6-audit-v1.md) 为准。** > > **2026-09-22 更正(终局改判)**:本文假设的终局——client 在 AVF pVM、链路 `AF_VSOCK`——**撤回**。现定终局: > client 与 server 可在**不同机器、不同 OS / 架构**上,经 **TCP** 连接;传输栈拆成控制面(`ITransport`)× 数据面(`ILink`) @@ -124,7 +124,7 @@ P6 该做的**不是**把它改成闩锁(那需要在每个 `[[noreturn]]` 站 |---|---|---| | `mobilegl_server_main` **在树上不存在**,只存在于 `ARCHITECTURE.md:488-496`;唯一的 `MobileGLServer` target 是 P0 spike 桩 | 实测 | `a6` 第 5 项(server 能否不链 `MG_Impl`)必须**真去链一次**,而不是再读一遍断言。三个阶段的文档都在断言它,没人试过 | | `MG_Config::Transport != Monolith` 有 **238 个**非测试站点:155 `MG_Backend` / 35 `MG_Impl` / 21 `MG_Remote` / 12 `MG_Pipe` / **8 `MG_State`** / 7 Config | 实测 | 此前每一次普查都漏了 `MG_State` 那 8 个。**~~它们是唯一不按目录成形的一批、同一个函数里同时问两个问题~~ a6 已推翻这个判断**:8 个里 2 个是注释,其余 6 个里 5 个是单问题测试,只有 `MipmapStorage.cpp:55-56` 一个真的同时问两个;而"同时问两个"的形状散在 `SlotAllocator` / `PipeFill` / `PipeApply` / `Managers` / `MG_Remote/Client` 的 **18 个站点**上。`a6` 仍应先分类这一族,但取样对象是那 18 个,不是 `MG_State` 的 8 个 | -| `m_windowHandle` **不止一个注入点**。a6 给出精确形状:全树 store 只有 4 处(`BackendObject.cpp:475`、`:365`、`:206`、`:207`),read 只有 5 处;`:283` 是一次**调用**不是 store;`.Width`/`.Height` **没有任何读者** | 实测 | "server 拥有显示"确实不是一处改动,但最小 latch 点比原先写的更清楚:`surfaceState->Window` 只在 `RegisterEGLWindowSurface`(`:232-238`)写一处。连同 `sameHandle` 去重键共四处,见 [`a6-audit-v1.md`](notes/p6/a6-audit-v1.md) §6 | +| `m_windowHandle` **不止一个注入点**。a6 给出精确形状:全树 store 只有 4 处(`BackendObject.cpp:475`、`:365`、`:206`、`:207`),read 只有 5 处;`:283` 是一次**调用**不是 store;`.Width`/`.Height` **没有任何读者** | 实测 | "server 拥有显示"确实不是一处改动,但最小 latch 点比原先写的更清楚:`surfaceState->Window` 只在 `RegisterEGLWindowSurface`(`:232-238`)写一处。连同 `sameHandle` 去重键共四处,见 [`a6-audit-v1.md`](a6-audit-v1.md) §6 | --- diff --git a/docs/Disaggregated/P6-SPAWN-PLAN.md b/docs/Disaggregated/notes/p6/P6-SPAWN-PLAN.md similarity index 98% rename from docs/Disaggregated/P6-SPAWN-PLAN.md rename to docs/Disaggregated/notes/p6/P6-SPAWN-PLAN.md index 8dbddfda..e802e4ef 100644 --- a/docs/Disaggregated/P6-SPAWN-PLAN.md +++ b/docs/Disaggregated/notes/p6/P6-SPAWN-PLAN.md @@ -2,7 +2,7 @@ > 历史规划基线 `feat/disaggregated @ f23fbc1b`。路径在 `MobileGL/` 下,旧行号在 `c6` 落地时重核。 > 契约草稿 [`P6-CONTRACT-DRAFT.md`](P6-CONTRACT-DRAFT.md),`c6` 落地时移为 `MobileGL/MG_Remote/CONTRACT-P6.md`。 -> **P5f 前提全部完成**(2026-09-20):零 `BARRIER_PULLED` 分类、双块 / strict 门、静态状态世代与生命周期、registry / 反向通道边界均已收口;阶段审查修复及最终 Redmi 六个 clean-boot 臂也已通过。见 [`P5F-WIRE-COMPLETENESS.md`](P5F-WIRE-COMPLETENESS.md)、[收口审查](notes/p5f/close-review.md)和[设备报告](notes/p5f/device-report.md)。 +> **P5f 前提全部完成**(2026-09-20):零 `BARRIER_PULLED` 分类、双块 / strict 门、静态状态世代与生命周期、registry / 反向通道边界均已收口;阶段审查修复及最终 Redmi 六个 clean-boot 臂也已通过。见 [`P5F-WIRE-COMPLETENESS.md`](../p5f/P5F-WIRE-COMPLETENESS.md)、[收口审查](../p5f/close-review.md)和[设备报告](../p5f/device-report.md)。 > > **P6 尚未开工:`a6`、`c6` 与 spawn 实现均未启动。** 下文保留历史包规划;标 `待 a6` 的 P6 进程边界事项仍需审计确认,P5f 完成不等于 P6 已实现。 @@ -30,7 +30,7 @@ 2. `ServerMain`。今天的 `MobileGLServer`(`CMakeLists.txt:923`)是 P0 spike 桩(`tools/spikes/server_stub/main.cpp`),Android-only、默认 OFF。 3. 进程机制:fork/execve;envp 剔除 + 子进程禁止递归选择 client transport(双保险);`dladdr` 兜底;有界重试握手;EOF 即退;不留孤儿。原“子进程强制 monolith”的具体实现**待 a6**:P5f 的 server record 臂依赖 active transport 选路,必须区分反递归与 backend server 角色选择,不能因此回到 frontend glue。 4. 控制面帧。**P5f 包 fc 已把帧化本身落地**:forwarder 改发 `SurfaceControlFrame` 值帧(`MG_Remote/Server/SurfaceControlFrame.h`),schema 缺口(3 枚枚举 + `readSurface`/`context` 字段 + `WindowKind::MetalLayer`)已按 append-only 补齐,wire 编解码与具名拒绝在 `MG_Remote/Protocol/SurfaceOpCodec.cpp`(`ServerApplyWireSurfaceOp`)。P6 的 `cp` 只剩传输:client 侧 `EncodeSurfaceOpFrame` 上 socket、server 泵进 `ServerApplyWireSurfaceOp`、`SurfaceReply` 回程与 §4 的死亡接线。P5c 审计 G4 行,原记 P6。 -5. 静态状态 / 生命周期的 spawn 验证,**不是重做 P5f fs**。unpack / render shadow 已按 native generation、served-context serial、执行臂组成的 `ContextEpoch` 失效;raw-depth sampler 按 native generation 重建。XFB 按 server lifetime id 隔离,served-context reset 保留仍活着的 paused/pending span,native context 销毁才清对象;server liveness 由控制生命周期持有。`st` / `a6` 核这些语义在第二进程与 socket 接线下仍成立,见草稿 §6 与 [fs 报告](notes/p5f/fs-report.md)。 +5. 静态状态 / 生命周期的 spawn 验证,**不是重做 P5f fs**。unpack / render shadow 已按 native generation、served-context serial、执行臂组成的 `ContextEpoch` 失效;raw-depth sampler 按 native generation 重建。XFB 按 server lifetime id 隔离,served-context reset 保留仍活着的 paused/pending span,native context 销毁才清对象;server liveness 由控制生命周期持有。`st` / `a6` 核这些语义在第二进程与 socket 接线下仍成立,见草稿 §6 与 [fs 报告](../p5f/fs-report.md)。 6. 死亡语义,见 §4。 ## 3 边界:P6 不做真窗口 diff --git a/docs/Disaggregated/notes/p6/README.md b/docs/Disaggregated/notes/p6/README.md new file mode 100644 index 00000000..cc8690b5 --- /dev/null +++ b/docs/Disaggregated/notes/p6/README.md @@ -0,0 +1,378 @@ +# P6 — spawn transport(2026-09-22 收官) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 十包全落地:a6 审计 → c6 契约(`MobileGL/MG_Remote/CONTRACT-P6.md`)→ lk `RingControl` 按写者重组 → so `SocketTransport`(AF_UNIX + SCM_RIGHTS)→ sm 两个独立启动的进程 → cp EGL 控制帧跨进程 → hs build stamp / abi 补检 / 真实 pid → dl device-lost 闩 + `Session::Fail` 漏斗 → st fb-slot memo 具名 Fatal → t6 `integration-spawn` 车道(102/102)+ per-role 日志。 +- 出口门 1–8 全达成:G1 0/0/0/0(`.text` `0xa52203`);spawn 与 split 名集合一致且同数绿;进程树恰多一个子进程;逐条 arm 证明;每包 red-once;**门 7** 真机配对 A/B 判读 **tie**(inproc vs spawn client CPU −0.15% / +0.16%,monolith 约贵 30%);**门 8** 三个强制性能数齐(门铃 tie、`SEG_STAGE` 字节/帧、分块后记录/帧)。 +- 真机:`OpenRA.DirectGLES` spawn ssim 1.0 连跑三次;DirectVulkan 分离路径的不确定分歧归 P7(后由 B3 证明是 wire 臂 frame-serial floor 不健全)。杀 server 从 123 s 的错误 `BarrierTimeout` 变为 5.7 s 的正确 `DEVICE LOST`。 +- 包计划、契约草稿与终局重审(2026-09-24 从上层移入):[`P6-SPAWN-PLAN.md`](P6-SPAWN-PLAN.md)、[`P6-CONTRACT-DRAFT.md`](P6-CONTRACT-DRAFT.md)(已被 `CONTRACT-P6.md` 取代)、[`P6-ENDSTATE-REVIEW.md`](P6-ENDSTATE-REVIEW.md)(其 AVF pVM / `AF_VSOCK` 终局已撤回,见页首更正)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P6** spawn transport +- **状态**:✅ **已收官(2026-09-22)**;全部包已落地于 `feat/disaggregated`(头 `77cbd176`,2026-09-21),出口门第 1–6 项此前已达成,第 7/8 项于 2026-09-22 采齐。收官前核出的三项:server 进程侧 `PipeStats` 从未初始化(`srv=0 srvpark=0` 是结构性零,门 8 无 server 半边)**已在收尾中修好**;184 符号棘轮 CI 门(契约 §12.1)与契约 §2 分给 hs 的 `LinkTerms` / `Refuse` / 指纹拆分**未落地**,移交 P6.5(负控 S6 随之) +- **落地什么 / 范围**:十包全落地:**a6** 进程装配/链接边界审计 → **c6** 规范契约 + 数据面缝惰声明 → **lk** `RingControl` 按写者重组 → **so** `SocketTransport`(AF_UNIX SOCK_STREAM + SCM_RIGHTS,12 条契约用例)→ **sm** 两个独立启动的进程(非 fork,name 汇合、共享匿名段,四守卫接线)→ **cp** EGL 控制帧跨进程并应答(`InitCapabilities` wire kind 11)→ **hs** build stamp 显式 present 入指纹 + `abiMajor/Minor` 补检 + 真实 pid → **dl** device-lost 闩(取自描述符挂断绝不取自超时)+ `Session::Fail` 漏斗(`FatalFamilies.def` 词表 + 90 站点归一 + `SessionFault` 帧命名家族)+ Fatal 普查门 → **st** fb-slot memo 具名 Fatal → **t6** `integration-spawn` 车道(102/102)+ 名集合结构性一致门 + per-role 日志(`.client.log` / `.server.log`)。Android CI 新增 `spawn-acceptance` 臂,App 从 `nativeLibraryDir` 解析 server 路径。真机 Redmi `2f7cbe2e` 上 `OpenRA.DirectGLES` spawn ssim=1.0 连跑三次 + 6 case 绿;`DirectVulkan` 真机分离路径不确定分歧(inproc 与 spawn 同表现→ Magma × 真实 GPU,非 P6,桌面 lavapipe 为 1.0)。边界仍为 pbuffer / surfaceless / 离屏;真窗口归 P12。契约 [`MG_Remote/CONTRACT-P6.md`](../../../../MobileGL/MG_Remote/CONTRACT-P6.md);a6 产出 [`a6-audit-v1.md`](a6-audit-v1.md)、[`a6-link-experiment.md`](a6-link-experiment.md)(**server 不能不链 `MG_Impl`**:184 符号,P7 101 / P3b-P4b 14 / 共有 60 / P6 自己 6);终局重审 [`P6-ENDSTATE-REVIEW.md`](P6-ENDSTATE-REVIEW.md)。**收尾四项(2026-09-22,工作树未提交)**:wire 计数器——`PipeStats` 新增 `ByteClass::StageSegmentBytes`(`PipeWireCodec.cpp:878` `StageAllocate` 唯一收口)与 `CallClass::WireRecords`(`:1188` `EncodeRecord` 提交路径)+ staged-blob 直方图,汇总行新增 `seg=` / `wrec=`;`ServerMain.cpp` 补 `PipeStats::Init()` / `Shutdown()`(spawn server 此前从不武装计数器——修前 0 条汇总行、修后 29 条,SSIM 1.0 不变);`MOBILEGL_PIPE_STATS_FILE` 按角色派生 `.client.json` / `.server.json`(走 `RoleLogPath` 同一导出规则,基名不指向文件);工具入库 `tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}` 与 `tools/trace_replay/run_android_retrace_local.py`(+19 行 `MOBILEGL_TRACE_PACKAGE`),`pin_device.sh` 新增 `2f7cbe2e` 条目(本板 GPU 钳 1050 MHz:`thermal_pwrlevel` 写 0 读回 1、`max_gpuclk` 只读 1 050 000 000;绝对值与 1100 MHz 时代的 `35d0befa` 行不可比)。 +- **验收门 / 证据**:出口门(契约 §9)1–6 ✅:G1 pull 0/0/0/0、`.text` 恒 `0xa52203`;`integration-spawn` 与 split 名集合一致(可比 102==102)且同数绿;进程树运行时恰多一子进程、结束为零;每条 spawn 条目记子进程 pid 与 `transport=spawn`(`--require-spawn` 门);每包 red-once。**7/8 已采(2026-09-22)**:**门 7** 真机 reboot-clean 同热窗口配对 A/B(Redmi `2f7cbe2e` / M332BF / SM8750 / Adreno 830v2,CPU 定频 little 1555200 / big 1958400,GPU 钳 1050 MHz(本板 `thermal_pwrlevel` 写 0 读回 1、`max_gpuclk` 只读 1 050 000 000;绝对值与 1100 MHz 时代的 `35d0befa` 行不可比,只有同会话配对可比),`minecraft-1.21.4-rd12-odinlite-in-world` + DirectGLES,~1471 draws/帧;六臂交错 × best-of-3:18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal`;client CPU p50 ms/帧 inproc 7.880/7.889 vs spawn 7.876/7.869,均值 **−0.15%**,符号随臂序翻转,臂内散布 0.2–4.2%;第二会话 inproc/spawn 交错 × best-of-2:4/4 OK、8/8 `PINNED`、0 `Fatal`,7.987/7.989 vs 7.992/8.009 = **+0.16%**)——判读 **tie**(差值在臂内噪声内),如实记录、不设门;monolith 基线约贵 30%(−29.8% / −29.9%)。**门 8①** socket 门铃 vs inproc condvar:client 121.1 waits/帧、5.6–6.1 parks/帧,两传输逐字相同(run total 30 401–30 407);server inproc 10 866–11 963 waits/帧、5.0–6.2 parks/帧,spawn 10 415–10 416 waits/帧、4.66–4.90 parks/帧——socket 阻塞读**既不更便宜也不更贵**(契约 §9「阻塞读可能更便宜」未兑现),spawn server waits 少 8.9–12.9% 归因于 handoff 纪律而非门铃本身,唯一 socket 略差的列是 `clipark/f`(6.07 vs 5.60/5.75)。**门 8②③**(inproc,真实 trace retrace):`SEG_STAGE` 字节/帧 OpenRA 130.8 KB median / 8.01 MB steady mean(含启动负载 8.07 MB),rd12 816.2 KB median / 1.53 MB steady mean(含负载 10.79 MB,rd12 首帧 981 654 draws vs 稳态 ~1471,run-wide mean 约 7× 稳态,两个读法都报);分块后记录/帧 OpenRA 211 median / 206 mean,rd12 7 497 median / 7 504 mean;`maxrec=1808` 与分块前相同(默认 8 MiB chunk 预算在这些负载上从未被打到)。证据 [`notes/p6/gate7-device-ab.md`](gate7-device-ab.md) / [`notes/p6/gate8-doorbell-device.md`](gate8-doorbell-device.md) / [`notes/p6/gate8-wire-counters.md`](gate8-wire-counters.md) + +## 收官快照(原 `CURRENT_STAGE_PROGRESS.md`,2026-09-22) + + + +**状态(2026-09-22):P6 已收官。** 全部包(a6…t6)落地,出口门第 1–8 项全部达成:门 7(真机 +`2f7cbe2e` reboot-clean、同热窗口配对 A/B)判读为 **tie**——`inproc` 与 `spawn` 的 client CPU/帧 +差 −0.15%(会话 1)/ +0.16%(会话 2),都落在臂内散布之内,如实记录、不设门;门 8 的三个强制 +性能数(socket 门铃 vs condvar、`SEG_STAGE` 字节/帧、chunking 后记录/帧)已采齐,逐数见 §2; +G1 pull 构建 **0/0/0/0** 且 `.text` 逐字节相同。测量期的源头:门 7 报告记 `75dd0cb1`、门 8 的 +wire 计数器与真机第二轮记 `94c130d6`。分支 `feat/disaggregated` 其后又落地两个 P6.5 设计提交 +(`872370ba`、`4e7bf0fe`),**现头 `4e7bf0fe`,与 `origin` 同步**;**工作树另有未提交的 P6 收尾 +改动**(wire 计数器、`ServerMain` 的 `PipeStats` 武装、`MOBILEGL_PIPE_STATS_FILE` 角色派生、设备行 +与工具入库、测试),见 §1.1。三种传输形态(`monolith` / `inproc` / `spawn`)在桌面(WSL lavapipe) +与真机(Redmi `2f7cbe2e`)上都跑通了真实 trace 的两进程 retrace,逐像素对上 golden。性能**只记录、 +不设门**。 + +--- + +### 1. 包状态 + +契约把 P6 切成 a6(审计)→ c6(契约)→ lk / so / sm / cp / hs / dl / st / t6。全部已落地: + +| 包 | 落地什么 | 状态 / 证据 | +|---|---|---| +| **a6** | 进程装配与链接边界的核验;198 行审计;184 符号的链接实验(server 不能不链 `MG_Impl`) | ✅ `c25a7760`;[`notes/p6/a6-audit-v1.md`](a6-audit-v1.md)、[`a6-link-experiment.md`](a6-link-experiment.md) | +| **c6** | 规范契约(21 条裁定)+ 数据面缝 `Transport/ILink.h` / `StreamLink.h`(声明不接线) | ✅ `b6293957`;`CONTRACT-P6.md` | +| **lk** | `RingControl` 按写者重组 + 可证伪的 per-field 断言;数据面缝的 pre-flight | ✅ `99649a90`、`db62d674` | +| **so** | `SocketTransport`(AF_UNIX SOCK_STREAM + SCM_RIGHTS),12 条 ITransport 契约用例 | ✅ `5f846d96` | +| **sm** | 两个**独立启动**的进程(非 fork),靠 name 汇合、共享匿名段;D1c 的六谓词里 D10 需要的三个,四个「编进来却永不武装」的守卫接线 | ✅ `70c45ab9`、`11807ac7`、`e3bb6d8b` | +| **cp** | EGL 控制帧跨进程并应答;`InitCapabilities` 上线(wire kind 11);真实 trace 两进程 retrace 逐像素通过 | ✅ `5239ae0c`、`7e244b37` | +| **hs** | build stamp 从「静默空串」变成显式 present 标志入指纹;客户端补检 `abiMajor/Minor`;`Hello::pid` / `Welcome::serverPid` 携带真实 pid | ✅ `90f9a790` | +| **dl** | device-lost 闩(取自描述符的挂断,绝不取自超时);D5b「alive-but-silent」具名诊断;`MOBILEGL_IPC_RESPAWN` 具名拒绝;`Session::Fail` 漏斗(`FatalFamilies.def` 词表 + 90 站点归一 + `SessionFault` 帧向对端命名家族);Fatal 普查门 | ✅ `557dc2c1`、`8b13d8a8`、`3399b879`、`3a12a48a`、`4bb4d4fa`、`b46da700`、`77cbd176` | +| **st** | fb-slot memo 的具名 Fatal(`nullptr==nullptr` 读成缓存命中、返回未填槽的潜在崩溃,sm 关掉可达性、st 出声) | ✅ `e3bb6d8b` | +| **t6** | `integration-spawn` 车道(102/102);集合一致做成结构性(一个宏两条臂)+ 校验门;per-role 日志 | ✅ `15f5b54b`、`5fb54f03` | + +配套:CI 的 `retrace-split` 从「1 case × 1 后端 × inproc」扩成「全 CI 矩阵 × 两后端 × inproc/spawn」, +`split` 键改成 opt-out;Android CI 新增 `spawn-acceptance` 臂,真机 App 从 `nativeLibraryDir` 解析 +server 路径(`dc5bd935`、`d83fc664`、`53406749`、`7c8120cc`)。 + +#### 1.1 收尾(2026-09-22) + +下列改动**在两轮测量(门 7 的 `75dd0cb1` 头、门 8 的 `94c130d6` 头)之后落地、目前仍在工作树里未 +提交**(其后两个提交 `872370ba`、`4e7bf0fe` 只改文档与 `Transport/StreamLink.h` 的注释,不含这些代码): + +- **wire 计数器**:`PipeStats` 新增 `ByteClass::StageSegmentBytes`(唯一收口点 + `PipeWireCodec.cpp:878` `StageAllocate`)与 `CallClass::WireRecords`(`:1188` `EncodeRecord` 提交 + 路径),外加一个 staged-blob 直方图(复用 `PayloadBucketOf` 的桶边界,只经 JSON dump 出口); + 汇总行新增 `seg=` / `wrec=` / `wrec/f=`(后者与 `draws/f=` 同形,无 Present 时按既有规则退回窗口总量)。 + 两个计数器都在 `#if MOBILEGL_PIPE_PUSH` 内,落点选在唯一收口点而不是调用方,否则等于维护一张 + "今天存在哪些 producer" 的名单。 +- **`ServerMain` 的 `PipeStats` 武装**:spawn server 此前从不武装计数器(红一次的证据: + **修复前 spawn server 0 条汇总行、修复后 29 条**,同一条 spawn trace、两侧 SSIM 均 1.000000)。 + `Init()` 放在 `MG_ConfigLoader::Init()` 与 `MGPipeSetServerProcessRole(true)` **之后**、 + `Shutdown()` 放在 `loop.Stop()` 之后 `_exit(0)` 之前——两处顺序都是承重的(前者让 latch 读到真 + 配置值,后者让 apply 线程不再发布时取运行总量与 JSON dump)。见 §2.1。 +- **`MOBILEGL_PIPE_STATS_FILE` 角色派生**:与日志 sink 同一套命名规则(库 `RoleLogPath` 导出、 + PipeStats 调用而非复制),dump 与 banner 都印/写 `.client.json` / `.server.json`, + **基名不再指向任何文件**——没跟上改名的读取方会当场 `ENOENT`,这是刻意的。这一条在 G1 上红了 + 三次才做对(无条件派生 → `.text +24`;加 `#if` 但两臂共用局部 → 1 symbol resized;两臂分离但 + 重写 `MGLOG_W` 格式串 → `.rodata +64`),最终形态是 pull 臂的代码逐语句原样、所有新增都在 `#if` 内。 +- **`pin_device.sh` 新增 `2f7cbe2e` 条目**:本板 GPU 钳 **1050 MHz**(`thermal_pwrlevel` 写 0 读回 1、 + `max_gpuclk` 只读 1 050 000 000、devfreq `max_freq` 同值),因此**绝对值与 1100 MHz 时代的 + `35d0befa` 行不可比**,只有同会话的配对可比。三种动作(`check` / `pin` / `unpin`)均已实测。 +- **工具进树**:`tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}`(真机配对 A/B 会话 + 运行器 + 只读渲染器,门 7 快照里的 `gate7_ab.py` / `render_gate7.py` 是它们的前身)与 + `tools/trace_replay/run_android_retrace_local.py`(+19 行):`MOBILEGL_TRACE_PACKAGE` 覆盖, + 让开发包能**装在既有 trace 包旁边**而不是替换它(两个不同密钥签的 APK 不能共用一个 id; + 缺省即原行为)。四个文件目前都在工作树里**未提交**。 +- **测试**:`PipeStats` **25/25**、全 unit **2367/2367**、`integration-spawn` **102/102**; + G1 复验 **0/0/0/0** 且 `.text` **逐字节相同**(pull 配置下两侧各 10 158 850 字节,`cmp` 无差异)。 + 逐条 red-once 与 G1 三次泄漏见 + [`notes/p6/gate8-wire-counters.md`](gate8-wire-counters.md) §9.7.3。 + +--- + +### 2. 出口门(契约 §9) + +| # | 门 | 状态 | +|---|---|---| +| 1 | **G1** pull 构建 0/0/0/0、`.text` 不变 | ✅ 符号 0 差异、`.text` 恒为 `0xa52203`(每次改动后复测) | +| 2 | **G2/G14** `integration-spawn` 名集合 == `integration-split` | ✅ `scripts/ci/spawn_lane_parity.py`:可比 102 == 102;名只增不删 | +| 3 | `integration-spawn` 与 split 同数绿 | ✅ 102/102 | +| 4 | 进程树:运行时恰多一个子进程、结束后为零 | ✅ `ServerSpawnTest.StartsAServerProcessAndHandshakesAcrossIt`(`CountOwnChildren`) | +| 5 | Arm 证明(ID-124):每条 spawn 条目记子进程 pid 与 `transport=spawn` | ✅ `--require-spawn` 门要求 ConfigLoader marker **且** `spawn ARMED - pid N` | +| 6 | 每包 red-once(R-16) | ✅ dl 的 S2/S7、hs 的 serverPid、sm/st 的守卫塌缩、t6 的名集合漂移均已跑红一次 | +| 7 | **设备**:Redmi reboot-clean、同热窗口配对 A/B | ✅ **配对 A/B 判读 tie**:会话 1(boot id `e69d0329…`→`107f24d3…`)六臂交错 `monolith,inproc,spawn,inproc,monolith,spawn` × best-of-3 —— 18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal{`;client CPU p50 ms/帧 **inproc 7.880/7.889、spawn 7.876/7.869**(均值 −0.15%,符号随顺序翻转,臂内散布 0.2–4.2%),`monolith` 约贵 30%。会话 2(boot id `107f24d3…`→`7e6c6e9f…`)inproc/spawn 交错 × best-of-2:4/4 OK、8/8 `PINNED`、0 `Fatal`;client CPU **7.987/7.989 vs 7.992/8.009**(+0.16%,亦在 0.14–0.69% 臂内散布内)。差值在臂内噪声内,**如实记录、不设门**。见 §3、[`notes/p6/gate7-device-ab.md`](gate7-device-ab.md)、[`notes/p6/gate8-doorbell-device.md`](gate8-doorbell-device.md) | +| 8 | **性能只记录**,但三个数强制:socket 门铃 vs condvar、`SEG_STAGE` 字节/帧、chunking 后记录/帧 | ✅ 三个数齐(见 §3.2 / §3.3 与 [`notes/p6/gate8-doorbell-device.md`](gate8-doorbell-device.md)、[`notes/p6/gate8-wire-counters.md`](gate8-wire-counters.md)):① socket 门铃 vs inproc condvar —— client **121.1 waits/帧**、5.6–6.1 parks/帧,两传输逐字相同(run total 30 401–30 407);server inproc **10 866–11 963** waits/帧、5.0–6.2 parks/帧(park 率 0.042–0.06%),spawn **10 415–10 416** waits/帧、4.66–4.90 parks/帧。判读(诚实):socket 阻塞读**既不更便宜也不更贵**,契约 §9「阻塞读可能更便宜」**未兑现**;spawn server waits 少 8.9–12.9% 归因于 handoff 纪律而非门铃本身;唯一 socket 略差的列是 clipark/f(6.07 vs 5.60/5.75,每帧多约 0.4 次 park)。② `SEG_STAGE` 字节/帧(inproc,真实 trace retrace):OpenRA **130.8 KB median / 8.01 MB steady mean**(含启动负载 8.07 MB),rd12 **816.2 KB median / 1.53 MB steady mean**(含负载 10.79 MB)。③ 分块后记录/帧:OpenRA **211 median / 206 mean**,rd12 **7 497 median / 7 504 mean**;`maxrec=1808` 与分块前相同,默认 8 MiB chunk 预算在这两条负载上**从未被打到** | + +`MEASUREMENTS.md:342` 的「分块后的逐 blob 字节分布尚无新测量」据此可关闭:门 8 第二轮给了 +默认预算下的分布(`maxrec=1808`,稳态 `seg/wrec ≈ 109 B/条`),结论是**默认配置下该分布就是未分块的 +原始分布**(分块在这两条负载上没有机会生效,由 `MOBILEGL_IPC_STAGE_MB=1` 的负控独立证明:seg 运行 +总量两臂逐字节相同 2 698 026 763,`wrec` 仅 +0.11%)。 + +负控(各跑红一次):S1 不可解析镜像(sm)、S2 kill server(dl)、S3 未洗环境(sm)、S5 窗口 token +到达(cp)、S6 **未跑——`LinkTerms.dataPlane` 不存在于树上**(`protocol.fbs` 无 `LinkTerms` / `Refuse`,hs 只落了 build stamp、`abiMajor/Minor` 补检与真实 pid;此处此前写"已跑红一次"是错的,随契约 §2 三行移交 P6.5,契约 §10.3)、S7 裸 abort(dl 普查门)、S8 `SessionFaultCount()==0` +整轮(t6)。 + +#### 2.1 出口门之外仍欠(2026-09-22 核出;出口门 1–8 本身已全部达成) + +- server 进程侧的 `PipeStats` 半边是**结构性零**:`ServerMain` 不跑 `MobileGL::Initialize`,`PipeStats::Init()` 从未在 server 进程执行,~80 处 `if (Enabled())` 永远为假,`srv=0 srvpark=0` 长得和"从没等过"一模一样(`PipeStats.h:256` 自己写明了这一点)——门 8 的"socket 门铃 vs condvar"因此没有 server 半边。**已修(工作树未提交,见 §1.1)**:在 server 角色初始化的唯一收口点于 config 加载与角色声明之后加 `Init()`,`loop.Stop()` 之后加 `Shutdown()`,汇总行落 `.server.log`;spawn server 汇总行 0 → 29 条,SSIM 1.0 不变。 +- 184 符号棘轮 CI 门(契约 §12.1):`test.yml` / `scripts/` 里**没有**。 +- 契约 §2 表 0 分给 hs 的 `LinkTerms` / `Refuse` / `wireFingerprint` 拆分:**未落地**,移交 P6.5(`ROADMAP.md` P6.5 行,契约 §10.3)。 +- `MOBILEGL_IPC_IDLE_EXIT_S`(`ARCHITECTURE.md` §15.3 默认 30):未被解析。 +- `TextureParamsWithoutASamplerView` 的"P6 inspection forwarder"债(`MEASUREMENTS.md` §7.2):场景代码已无 P6 引用,是否已由 fc / cp 关闭**待核**。 + +--- + +### 3. 真机验收(Redmi `2f7cbe2e`) + +trace APK 以 `-Pmobilegl.buildDisaggregated=ON` 构建,调试签名安装。功能全绿: + +- **`OpenRA.DirectGLES` spawn**:ssim=1.000000、mismatchPixels=0、`Fatal{` 零条,连跑三次; + `--require-spawn` 确认 `server role in pid N`。 +- 另有 6 个 case(含 in-world、BSL 光影)spawn 下通过。 +- **monolith** 控制组 ssim=1.0;证明门确认 monolith 只写 `mobilegl.client.log`、无 server 日志。 + +**如实记录、非 P6 引入**:`DirectVulkan` 在真机分离路径下画面分歧且**不确定**(inproc 与 spawn +抽自同一组结果:0.988998/0.976494…,而同 pbuffer 的 monolith 是 1.0)。两种 transport 表现一致, +故是 Magma 分离路径在真实 GPU 上的问题,P6 既未引入也修不了;桌面 lavapipe 上 DirectVulkan spawn 为 1.0。 + +那个会杀死 server 的 llvmpipe case(`iterationrp`,`LLVM ERROR: Cannot select vcvtps2ph`):dl 落地后 +从 123 秒的错误 `BarrierTimeout` 变成 **5.7 秒**的正确 `DEVICE LOST`(`revents=0x2010` = POLLRDHUP|POLLHUP)—— +仍红(驱动 bug),但红得诚实。 + +配对 A/B、门铃与 wire 计数器三张表的完整版在下文「实测」节 §12.2–§12.5(原快照里的 §3.1–§3.3 是它们的摘录,已去重)。 + +### 4. 日志按角色分文件(本阶段的一处基础设施改动) + +spawn 的两个进程原先 O_APPEND 追加同一个 `MOBILEGL_LOG_FILE_PATH`(正确但两个会话混在一处)。现在 +库按**调用线程角色**写 `.client.log` / `.server.log`(inproc 下 apply 线程即 server 角色, +同样分开)。基名不再指向任何文件——这是刻意的:没跟上改名的读取方会「文件不存在」当场报错,而不是 +半读通过。34 个消费方全部改到派生路径;拒绝普查读两份;派生规则由库 `RoleLogPath` 导出,不复制。 +G1 守卫下 pull 构建不变。 + +--- + +### 5. 已知未决 / 明确不属于 P6 + +- **`DirectVulkan` 真机分离路径分歧**——Magma × 真实 GPU,非 P6。见 §3 与 `ROADMAP.md` P7 行。 +- **P6.5**:传输栈两轴化(控制面 `ITransport` × 数据面 `ILink`,握手协商、可混搭)+ TCP 跨机 + wire 定宽与布局摘要;`StreamLink` 已声明未接线;`SEG_STAGE` 送窗口复活。终局已改为 TCP 跨机跨平台(2026-09-22),AF_VSOCK 撤回;见 `ROADMAP.md` P6.5 行、`ARCHITECTURE.md` §11.9。 +- **Ph**:让 `Session::Fail` 可返回的策略翻转(需在每个依赖 `[[noreturn]]` 的站点造真实返回路径)+ 配对 / 认证;前置于 P12 的非 loopback 监听。 +- **P12**:真窗口跨进程(`ANativeWindow*` 是客户端进程内指针,`SetWindowHandle` 至今具名拒绝)。 +- **seq/op 未穿 SessionFault 帧**:90 个站点不逐一穿线,消息本身已带 op,帧只带 code/family/message。 + +两条**结构性限制**,随收尾一起生效、如实记录(不是缺陷但会绊住读者): + +- **spawn client 无 `OnPresent`** → 它的窗口字段 `frames=0 window=0`,于是 + `draws=` / `wrec=` / `bytes[...]` / `gates[...]` 与**所有 per-frame 字段**在 spawn client 上都印 0 或 + `n/a`,**无效**;但 `PublishGauge` 是 **store 不是 add**,所以 `cli` / `clipark`、`maxrec` / + `maxcap` / `ringwraps` / `ringpads` / `ringwaits`,以及发射侧直接 `Add` 的 + `resid` / `csob-blob` / `seg` / `wrec` 的 **run total 有效**(窗口归属只有最后一段)。要取 spawn 的 + 窗口化 client 数必须让 client 进程到达一次帧边界——那会与 backend 的帧边界重复计数,需单独判断, + 不在本阶段。**逐 role 的取数规则**因此是:`srv` 对读 server.log、`cli` 对读 client.log, + spawn client 只有 `Shutdown` 终行(benchmark 模式)或 0 行(snapshot 模式)。 +- **`MOBILEGL_PIPE_STATS_FILE` 已改角色派生**:dump 与 banner 都写/印 + `.client.json` / `.server.json`,**基名不再指向任何文件**(与日志 sink 同一套 + `RoleLogPath` 规则,刻意让没跟上的读取方当场 `ENOENT` 而不是半读通过)。inproc 只产出 + `.client.json` 是**正确**的(服务端角色是线程不是进程,`Shutdown()` 只跑一次)。仓库内目前没有 + 这样的读者(`grep` 确认),但任何新写的车道/脚本都要读派生名。语义未变:仍是「在这里写 dump」, + 只是「这里」由库按角色展开成两个名字。 + +## 实测:P6 出口测量(2026-09-22,Redmi `2f7cbe2e` + WSL 桌面)(原 `MEASUREMENTS.md` §12) + +P6(spawn transport)的实现包(a6 / c6 / lk / so / sm / cp / hs / dl / st / t6)已全部落地,出口门 1–6 的逐项证据见 `CURRENT_STAGE_PROGRESS.md`;本节记录门 7(真机配对 A/B 热窗口)与门 8(三个强制性能数)在 2026-09-22 采齐的实测,以及支撑它们的收尾代码与工具。按本文件既有口径,下面所有性能数**只记录、不设门**:门 7 的判读是 tie,门 8 的三个数不带阈值。 + +### 12.1 协议(本节两个设备会话共同遵守) + +| 项 | 值 | +|---|---| +| 设备 | Redmi `M332BF`,serial `2f7cbe2e`,SM8750 / Adreno 830v2 | +| reboot-clean | `adb reboot` + `sys.boot_completed=1`,**boot id 前后比对**(id 变了才算数,见 §12.2) | +| CPU 定频 | little `policy0` → 1555200,big `policy6` → 1958400(`pin_device.sh 2f7cbe2e pin`;每臂前 `pin`、后 `check`,非 `PINNED` 即作废该臂,不是降级) | +| GPU 钉频 | kgsl `min/max_pwrlevel = 0` → **1050000000 Hz**(可比性警告见 §12.7) | +| 风扇 | `/sys/class/xm_power/hw_monitor/pwm_fan` `target_level=2` | +| 同热窗口臂交错 | 每个会话都在**一次 boot 内的唯一热窗口**里按给定顺序交错跑完,臂间有冷却 | +| 负载 | `minecraft-1.21.4-rd12-odinlite-in-world`,DirectGLES,251 帧,**1471.36 draws/帧** | +| 跑法 | `--benchmark-repeats 3`(门 7)/ `2`(门 8),`--benchmark-tail-frames 200`,`--benchmark-no-finish`,best-of-3 / best-of-2 | +| split 开关 | `MOBILEGL_IPC_ROLE_SPLIT_STATE=1`、`MOBILEGL_IPC_STRICT_ERRORS=1`、`MOBILEGL_IPC_RUN_AHEAD=1` | +| stats | `MOBILEGL_PIPE_STATS=1`、`MOBILEGL_PIPE_STATS_PERIOD=120` | +| 源码头 | 门 7 会话 `75dd0cb1`;门 8 两会话 `94c130d6` + §12.6 的收尾改动(工作树) | +| 臂的证明 | 每个 spawn 臂必须在**自己的**私有日志里出现 `Config: MOBILEGL_TRANSPORT=spawn` 与 `spawn ARMED - the server role runs in pid N`;`--transport` 在 `--benchmark` 模式下被 runner 忽略(会静默跑成 monolith),所以传输一律用显式 `--env MOBILEGL_TRANSPORT=…`,跑完按库自己的日志核对 | + +**主指标 = client GL 线程自身的逐线程 CPU ms/帧**(§9 的规则:内核对本 app 的 `sched_setaffinity` 请求不生效,进程总量会把两个时钟域混在一起),由设备在**尾 200 帧**上汇总;`fps` 与 wall p50/p95 并列记录,不作主指标。 + +**帧分母取自哪条日志。** `frames=` 由 `PipeStats::OnPresent` 递增,而它是 backend 的帧边界:`spawn` 下 present 在 **server 进程**被应用,所以 spawn **client 的 `frames=0 window=0`**——它一个窗口都不推进。因此费率一律除以**真正数到帧的那条日志**的 `frames=`(inproc 取 client log,spawn 取 server log,两者都是 251),**不得**除以同一行的 `window=`,那会把费率抬高 run/window(本负载约 21×),得到一个看似合理而错的数。同理: + +- spawn client 行里的 `srv=0 srvpark=0` 与 spawn server 行里的 `cli=0 clipark=0` 都是**「另一个进程」的零,不是「从未等待」**;`srv`/`srvpark` 由 apply 线程发布进**它自己进程**的 PipeStats,`cli`/`clipark` 由 client 的发射器发布进 client 进程。 +- spawn client 的 `cli`/`clipark` 仍然可信,因为 `EmitPresent` 走 `PublishGauge`(**store 不是 add**),run total 与窗口无关;而它的窗口化字段(`draws=`、`wrec=`、`bytes[...]`、`gates[...]` 及所有 `*/f`)在同一次运行里**无效**。 +- spawn client 整轮只有 `Shutdown` 的**一条终行**(benchmark 模式)或**一条都没有**(snapshot 模式:该模式在快照完成后抛出退出,跳过 `retrace::cleanUp()` → `MobileGL::Destroy()` → `PipeStats::Shutdown()`)。 +- `* total ms` 那类**每角色运行总量**包含第 1 帧的 shader 编译(本 fixture 的帧 1 花 **7332 ms** client CPU,其余 250 帧各 7.7–8.5 ms),**不是**每帧率,不得除以 251;它跨臂差 4.5–6.7% 与传输无关,这正是主指标取「尾 200 帧的逐帧值」的原因。 + +### 12.2 门 7:配对 A/B(两会话) + +**会话 1**(boot id `e69d0329-2264-4a2d-9b00-e1a4ffb6154d` → `107f24d3-b1f1-4a97-ab8c-9706d1901f32`):六臂交错 `monolith, inproc, spawn, inproc, monolith, spawn` × best-of-3,**18/18 runner 退出 0、36/36 pin check `PINNED`、0 `Fatal{`**,12 次 pin check 的 `cpuss-0-0` 温度 37.6–46.5 °C。 + +| 臂 | 顺序 | wall p50 ms | **client CPU p50 ms/帧** | fps | 臂内散布 | +|---|---|---:|---:|---:|---:| +| monolith (a) | 1 | 11.320 | **11.231** | 86.32 | 1.0% | +| inproc (a) | 2 | 7.971 | **7.880** | 106.04 | 0.3% | +| spawn (a) | 3 | 7.989 | **7.876** | 106.12 | 2.2% | +| inproc (b) | 4 | 8.001 | **7.889** | 105.78 | 0.2% | +| monolith (b) | 5 | 11.287 | **11.240** | 86.26 | 1.3% | +| spawn (b) | 6 | 7.979 | **7.869** | 105.95 | 4.2% | + +**会话 2**(boot id `107f24d3-b1f1-4a97-ab8c-9706d1901f32` → `7e6c6e9f-1894-4916-bf39-cce48c9abd37`):只跑 inproc / spawn 交错 `inproc, spawn, spawn, inproc` × best-of-2(聚焦门 8 ①,见 §12.3),**4/4 runner 退出 0、8/8 pin check `PINNED`、0 `Fatal{`**,温度 37.2–44.9 °C。 + +| 臂 | 顺序 | wall p50 ms | **client CPU p50 ms/帧** | fps | 臂内散布 | +|---|---|---:|---:|---:|---:| +| inproc | 1 | 8.105 | **7.987** | 105.72 | 0.14% | +| spawn | 2 | 8.116 | **7.992** | 104.43 | 0.34% | +| spawn | 3 | 8.113 | **8.009** | 104.19 | 0.26% | +| inproc | 4 | 8.089 | **7.989** | 103.72 | 0.69% | + +**配对差(spawn − inproc)**,两个顺序都列,不挑好看的那个: + +| 会话 | 指标 | inproc (a / b) | spawn (a / b) | Δ(均值) | Δ% | +|---|---|:---|:---|---:|---:| +| 1(best-of-3) | **client CPU p50 ms/帧** | 7.880 / 7.889 | 7.876 / 7.869 | **−0.012** | **−0.15%** | +| 1 | wall p50 ms | 7.971 / 8.001 | 7.989 / 7.979 | −0.002 | −0.03% | +| 1 | fps | 106.04 / 105.78 | 106.12 / 105.95 | +0.125 | +0.12% | +| 2(best-of-2) | **client CPU p50 ms/帧** | 7.987 / 7.989 | 7.992 / 8.009 | **+0.0125** | **+0.16%** | + +**monolith 基线行**(不属配对,按协议记录): + +| 比较 | client CPU p50 ms/帧 | Δ | fps | Δ | +|---|---|---:|---|---:| +| monolith → inproc | 11.236 → 7.885 | **−29.8%** | 86.29 → 105.91 | +22.7% | +| monolith → spawn | 11.236 → 7.873 | **−29.9%** | 86.29 → 106.04 | +22.9% | + +**判读:tie。** 会话 1 的差值(−0.15%)落在臂内散布 0.2–4.2% 之内,且 wall 列的**符号随顺序翻转**(顺序 (a) inproc 领先 0.03%、顺序 (b) spawn 领先 0.03%),这是噪声的特征而不是效应的特征;四个 inproc / spawn 臂的 client CPU p50 都落在 7.87–7.95。会话 2 在同一负载、不同构建与不同 boot 上复现了这个 tie(+0.16%,臂内散布 0.14–0.69%,差值比它取自的臂内散布低一个数量级)。两会话之间移动的是绝对水平(约 0.1 ms),那是运行间漂移,不是传输效应。**两条 transport 在 client 每帧 CPU 上不可区分,两者都比 monolith 约便宜 30%**——spawn 是通过 socket 拿到这个数的,**这条负载的分辨率看不见 socket 门铃的代价**。性能只记录,**不设门**。 + +### 12.3 门 8① socket 门铃 vs inproc condvar + +四个计数器:`srv` / `srvpark` = apply 线程进入 `Doorbell::Wait` 的次数 / 其中停止自旋转为阻塞的次数;`cli` / `clipark` = client 生产者的同一对。**两对都是运行总量**,所以费率除以该轮运行的 `frames=251`,**不是**同一行的 `window=`(见 §12.1)。 + +**会话 2 的完整账**(这次 spawn 的 server 半边也在——见 §12.6 的修复): + +| 臂 | 传输 | counters 取自 | frames 取自 | `cli frames=` | `srv frames=` | srv | srvpark | cli | clipark | **srv/f** | **srvpark/f** | **cli/f** | **clipark/f** | +|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| 01 | inproc | client log(同进程两角色一行) | client log | 251 | 240 | 2 869 874 | 1 341 | 30 407 | 1 444 | **11433.8** | **5.343** | **121.1** | **5.753** | +| 02 | spawn | client(cli) + server(srv) | server log | 0 | 251 | 2 614 177 | 1 229 | 30 401 | 1 524 | **10415.0** | **4.896** | **121.1** | **6.072** | +| 03 | spawn | client(cli) + server(srv) | server log | 0 | 251 | 2 614 456 | 1 170 | 30 401 | 1 525 | **10416.2** | **4.661** | **121.1** | **6.076** | +| 04 | inproc | client log | client log | 251 | 240 | 3 002 771 | 1 265 | 30 407 | 1 406 | **11963.2** | **5.040** | **121.1** | **5.602** | + +**会话 1 的账**(同一个窗口里的另一轮,spawn 的 server 半边当时**采不到**): + +| 臂 | 传输 | srv | srvpark | cli | clipark | **srv/f** | **srvpark/f** | **cli/f** | **clipark/f** | +|---|---|---:|---:|---:|---:|---:|---:|---:|---:| +| inproc (a) | inproc | 2 738 247 | 1 543 | 30 407 | 1 548 | **10909.4** | **6.148** | **121.1** | **6.169** | +| spawn (a) | spawn | **不可得** | **不可得** | 30 401 | 1 538 | — | — | **121.1** | **6.127** | +| spawn (b) | spawn | **不可得** | **不可得** | 30 401 | 1 530 | — | — | **121.1** | **6.096** | +| inproc (b) | inproc | 2 727 377 | 1 281 | 30 407 | 1 399 | **10866.0** | **5.103** | **121.1** | **5.574** | + +会话 1 的 spawn 半边不可得是**结构性**的、不是漏采:spawn server 进程当时从不跑 `PipeStats::Init()`(它只从 `MobileGL::Initialize()` 调用,而 spawn server 走的是 `MG_ConfigLoader::Init()` + `MG_Backend::InitServerRoleForSpawn()`),于是该进程里所有 `PipeStats::Enabled()` 门恒为 false,**一条汇总行都没有**——不是携带 0,是整行不存在。client 日志里的 `srv=0 srvpark=0` 因此是「另一个进程」的零。**该缺陷已在工作树修好**(§12.6),会话 2 就是修后的复采。 + +**逐列读法:** + +- **`srv/f`(server 会合率)**:inproc `10 866–11 963`,spawn `10 415–10 416`;折算成每 draw,spawn 两臂各 **7.1**,inproc 两臂 **7.8 / 8.1**(1471.36 draws/帧;会话 1 的 inproc 两臂相应是 **7.4**)。spawn 比 inproc **低 8.9–12.9%**,但这不是传输的胜利:两个计数器数的**不是同一个事件**——inproc 下 apply 线程的 `Doorbell::Wait` 同时是同地址空间内发布 client 队列排空的机制、client 在 verb barrier 后面大段同步,spawn 下同一个循环是被描述符就绪唤醒;每帧两个角色必须达成一致的次数是**handoff 纪律**的性质,不是门铃的代价。可比的是 park 比例,而它两边几乎相同。 +- **`srvpark/f`(会合真的阻塞了多少)**:会话 2 inproc 5.343 / 5.040、spawn 4.896 / 4.661;会话 1 inproc 6.148 / 5.103(spawn 不可得)。两个会话合起来即**服务端 5.0–6.2 parks/帧、其中只有 0.042–0.06% 的等待进入阻塞**(会话 2 四臂逐臂 1 341/2 869 874 = 0.047%、1 229/2 614 177 = 0.047%、1 170/2 614 456 = 0.045%、1 265/3 002 771 = 0.042%;并入会话 1 的 inproc 两臂后上界到 0.056%)。自旋预算在两条传输上几乎覆盖全部会合——P5d/P5e 的结论在这里再次成立:代价是**会合本身**,不是唤醒。 +- **`cli/f`(client 自己的等待)**:**四个臂上都是 121.1**(会话 1 的四个臂也一样,运行总量 30 401–30 407 次),逐字到十分位。client 两种传输做同样的工作,传输不改变它**多久**等一次。 +- **`clipark/f`(client 的 park)**:**唯一 socket 略差的列**——会话 2 spawn 6.07(6.072 / 6.076)对 inproc 5.75 / 5.60,每帧多约 **0.4 次 park**(~121 次等待里的 0.4 次)。client 等待的 **4.6–5.1%** 会 park,比服务端高两个数量级,因为 client 的自旋预算花在一个经常还没到的生产者上。 +- **tie 判读**:client CPU 差 **+0.16%**(会话 2)落在 0.14–0.69% 的臂内散布内,会话 1 是 −0.15%;契约 §9 那条注记(「把 ~1600 spins/帧 转成 ~4 次阻塞读,**可能更便宜**」)的观测对象正是 client CPU ms/帧,而它**两个方向都没动**。**结论是诚实的:socket 阻塞读既不更便宜、也不更贵**——该注记的「更便宜」未兑现。注记里的 1600 spins/帧 是更重场景的数,本机 rd12 的参照是服务端 ~10 400–12 000 次会合/帧、client 121 次/帧;park 比例 0.042–0.06%(服务端)与 4.6–5.1%(client)也说明本场景的自旋几乎全都奏效。 +- 两条传输**都比 monolith 约便宜 30%**(§12.2),spawn 走的是一条 socket 而不是共享地址空间。 + +### 12.4 门 8②③:`SEG_STAGE` 字节/帧、分块后记录/帧、`maxrec` 与逐 blob 分布 + +口径:`MOBILEGL_PIPE_STATS=1` + `MOBILEGL_PIPE_STATS_PERIOD=1`(每个 `eglSwapBuffers` 一行,`window=1` 恰一帧),车道 = inproc + `MOBILEGL_IPC_ROLE_SPLIT_STATE=1` + `MOBILEGL_IPC_STRICT_ERRORS=1` + `MOBILEGL_IPC_RUN_AHEAD=1`,后端 DirectGLES,真实 trace retrace。两个负载:**OpenRA**(`MobileGLTraceReplay.OpenRA.DirectGLES.SPLIT`,SSIM **1.000000**)与 **rd12-odinlite-in-world**(无 SPLIT ctest 条目,按同一命令行手工驱动,SSIM **0.999998**)。新增的两个计数器都落在**唯一的收口点**上:`SEG_STAGE` 字节记在 stage 分配器(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:878`,`StageAllocate`),记录数记在记录提交路径(`MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1188`,`EncodeRecord` 成功路径)——所以覆盖是构造性的,不是一张「今天有哪些 producer」的名单。(两处行号取 §12.6 收尾改动后的工作树内容。)这两个数在 **inproc 车道**采集;spawn 下 client 的字节 / 记录类同样在采(它们由 client 的发射器 Add,run total 可信),但 spawn 的**服务端**字节 / 记录类要等 §12.6 的 `ServerMain.cpp` 修复之后才存在。 + +**framing 警告(两个读数都报)**:`PERIOD=1` 下**第一行覆盖整个启动 / 加载窗**——rd12 的帧 1 有 **981 654 draws**、5 053 895 条记录、`seg=2 317 939 359`,而其余每帧只有约 1 471 draws。所以「窗口求和 / 帧数」是**含加载的均值**;报这个数时必须说清是哪个口径。rd12 的加载窗把 run-wide 均值抬到稳态均值的**约 7 倍**(10.79 vs 1.53 MB/帧);OpenRA 两个值接近(8.07 vs 8.01 MB/帧)是因为它的加载窗与稳态窗同量级。 + +| 口径 | OpenRA | rd12 in-world | +|---|---:|---:| +| **`SEG_STAGE` 字节/帧——稳态中位数** | **130.8 KB/帧**(130 848 B) | **816.2 KB/帧**(816 248 B) | +| **`SEG_STAGE` 字节/帧——稳态均值**(丢弃前 10% 窗) | **8.01 MB/帧**(8 012 890 B) | **1.53 MB/帧**(1 534 555 B) | +| `SEG_STAGE` 字节/帧——含启动负载的均值 | 8.07 MB/帧(8 068 255 B) | 10.79 MB/帧(10 792 107 B) | +| **记录/帧(分块后)——稳态中位数** | **211** | **7 497** | +| **记录/帧(分块后)——稳态均值** | **206** | **7 504** | +| 记录/帧——含启动负载的均值 | 196.8 | 27 689.5 | +| 参考:`draws` 稳态中位数 | 30 | 1 471 | +| `maxrec` / `maxcap`(运行总量) | 1808 B / 4 194 304 B(0.043%) | 1808 B / 4 194 304 B(0.043%) | +| 运行总量 `seg` / `wrec`(帧数) | 233 979 394 / 5 706(29 帧) | 2 698 026 763 / 6 922 378(250 帧) | +| `ringwraps` / `ringpads` / `ringwaits` | 0 / 0 / 0 | 61 / 53 / 0 | + +读法: + +- **两个负载的稳态形状完全不同,这正是必须分开报的理由**:OpenRA 稳态的 `tex` 中位数是 32 768 B 而均值 5.6 MB(少数窗有巨大的纹理上传尖峰),`seg` 的中位数 / 均值因此差 **61×**;rd12 稳态的 `tex` 中位数为 0(多数帧不上传纹理),差 **1.9×**。只报一个数就会掩盖另一个负载的形状。按本文件同类数字的惯例,稳态中位数与稳态均值这对都列出来。 +- **`maxrec=1808` 与分块前完全相同,原因不是「分块没生效」而是「分块没有机会生效」**:默认 8 MiB chunk 预算(`MGPipeStageChunkBytes()` = `clamp(segment/4, 4096, segment)`,默认 32 MiB → 8 MiB)在这两条负载上从未被打到,所有 blob 都是原始尺寸。稳态 `seg/wrec ≈ 816 248/7 497 ≈ 109 B/条`,与全局的 390 B/条都在字节级别。§6.3 记的分块前 `maxrec=1808` / `SetVertexAttribDefaults` 因此原样成立;`ringwaits=0`(250 帧一次都没等过 retiredSeq)与 rd12 稳态 `seg` 中位数对 32 MiB 的 `SEG_STAGE` 约 40 帧余量一致。 +- **`wrec` 是窗口计数**(`PERIOD=1` 下每窗恰一帧,所以 `wrec` 与 `wrec/f` 同值),且 `PERIOD=1` 每帧印一行 INFO 本身有可观开销——**这些帧数不能当性能数**,只用于按帧摊平字节与记录。 +- **交叉核对(能排除什么、不能排除什么)**:`seg` 记 wire 写进 `SEG_STAGE` 的字节,后端的 `buf`/`tex`/`csob-blob` 记往驱动搬的字节,两个总体不同、不要求相等,但应在同一量级且同步起伏。逐窗(250 窗)比对:`seg` 中位数 818 030 / 均值 10 792 107,`parts`(= buf+tex+csob-blob)中位数 291 840 / 均值 3 978 204,均值比值 2.713,每条记录 389.8 B;两者在同一些窗一起抬升、同一些窗一起落底,没有出现「一个动另一个不动」的窗。比值 > 1 有解释(`seg` 稳态恒有 `csob-blob ≈ 281 KB/帧` 的 program archive 只走 wire 不经后端字节类,加上 persistent-map 推送与 tail 字节)。这**不能证明覆盖完整**(没有第三方真值),能排除的是「`seg` 明显偏离同一批字节」这一整类错误。 +- **逐 blob 字节分布的首次测量**:答案是「默认配置下该分布就是未分块的原始分布」。取数车道是 **integration**(`ctest -R "^DirectGLES\.(Arena|Ssbo|Buffer|Atomic|Readback)"`,19/19 通过),经 `MOBILEGL_PIPE_STATS_FILE` 的 JSON dump——**retrace 车道拿不到 dump**,因为 snapshot 模式在快照完成后抛出退出、跳过了 `retrace::cleanUp()` → `MobileGL::Destroy()` → `PipeStats::Shutdown()`(§12.1),所以 dump 只能在 benchmark 模式或 integration 车道取。该 dump:**70 records**、`seg=5768 B`,直方图复用了 draw payload 的桶边界(bucket 0 = 0 字节,bucket n>0 = [2^(n-1), 2^n))。**这里有两个不同的计数,必须写清,否则会被读成同一个数**:**非空 bucket 数 = 8**(落在 bucket 3–12),**blob 条数 = 12**;也就是说「**8 blobs / bucket 3–12**」这句简写里的 8 指的是**有内容的桶数**,而逐桶计数合计是 12 条 blob。逐桶:bucket 3 [4,8) 4 条、4 [8,16) 1 条、5 [16,32) 2 条、7 [64,128) 1 条、9 [256,512) 1 条、10 [512,1024) 1 条、11 [1024,2048) 1 条、12 [2048,4096) 1 条(4+1+2+1+1+1+1+1 = 12)。这批 integration 用例的 `frames=0`(跑 swap 但不走 Present 计数),所以窗口字段无意义,分布本身是**运行总量**(分布没有「窗口」可言)。 + +### 12.5 负控:`MOBILEGL_IPC_STAGE_MB=1`(256 KiB chunk 预算) + +要让分块真正发生,唯一的办法是把 stage 预算人为压小;同一条 rd12 trace、同一个库、只改这一个环境变量的 A/B: + +| rd12 in-world,inproc split | `STAGE_MB=32`(chunk 8 MiB) | `STAGE_MB=1`(chunk 256 KiB) | 变化 | +|---|---:|---:|---| +| SSIM | 0.999998213 | 0.999998213 | 不变 | +| **`seg` 运行总量** | **2 698 026 763** | **2 698 026 763** | **逐字节完全一致** | +| `seg` 稳态中位数 | 816 248 | 554 104 | −32% | +| **`wrec` 运行总量** | 6 922 378 | 6 929 736 | +7 358(**+0.11%**) | +| `wrec` 稳态中位数 / 均值 | 7 497 / 7 504 | 7 497 / 7 508 | +0.05% | +| `tex` 运行总量 | 641 567 296 | 1 886 392 128 | **+194%** | +| `buf` / `csob-blob` / `draws` | 见 §12.4 | 与 `STAGE_MB=32` 逐项相同 | 不变 | +| `maxrec` / `maxcap` / `ringwraps` / `ringpads` | 1808 / 4 194 304 / 61 / 53 | 同左 | 不变 | + +这是**它自己那个问题的负结果,如实记录**: + +- **`seg` 运行总量两侧逐字节相同**说明该计数器测的是**生产者交出来的内容**,与发射侧怎么切、arena 开多大**完全无关**——这正是把落点放在唯一收口点的直接证据:若记在 emitter 侧或记成 arena 占用,这一行就会跟着预算动。 +- **稳态中位数 −32% 与运行总量不变同时成立**:同样的总字节被**重新分到不同的窗口**(预算变小后,原本一次完成的上传被切成多次、跨帧重传),所以**逐窗归属会动、总量不会**。读数应以运行总量或明确声明的窗口口径比较,**不要**拿两套配置的单窗中位数直接对比。 +- **`wrec` 只涨 0.11%**(+7 358 条 / 250 帧 ≈ +29 条/帧,稳态中位数两侧相同)说明这条 trace 的内容本来就没有超过 256 KiB 的 blob,即使预算压到 256 KiB 也几乎无可切;`maxrec` 仍是 1808,第三次确认(`maxrec` 是单条**记录**的字节上界,blob 被切不改变它)。 +- **`tex` +194% 是这次 A/B 真正的发现,但不作结论**:`MOBILEGL_IPC_STAGE_MB` 同时是 **arena 大小**与 chunk 预算的来源,压小它改变了纹理 slab 的切分与重传行为(整宽 slab 被预算切碎后每片各自重传)。这一项**不在门 8 的定义里**,且**没有隔离到单变量**(arena 大小与 chunk 预算被同一个环境变量绑在一起),因此只作为 A/B 的可解释性证据记录,**不作结论**;要分开这两个效应需要把二者拆成可独立配置的量,那是另一个包的事。 +- 因此门 8 的「chunking 后记录/帧」取**默认配置**的 §12.4 值。 + +### 12.6 支撑本节的收尾代码与工具(工作树,未提交) + +| 改动 | 位置 | 内容与证据 | +|---|---|---| +| 新计数器 `ByteClass::StageSegmentBytes` | `MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:878`(`StageAllocate`) | stage 分配器的**唯一收口**;摘要行 `bytes/f[...]` 里印 `seg=` | +| 新计数器 `CallClass::WireRecords` | `MobileGL/MG_Remote/Wire/PipeWireCodec.cpp:1188`(`EncodeRecord` 提交路径) | 记录提交路径的**唯一收口**(`Reserve` 成功、正文写完、blob 槽诚实性检查之后、`++m_emitSeq` 之前);摘要行印 `wrec=` 与 `wrec/f=`,与 `draws/f=` 同形、按 `perFrame` 规则联动 | +| staged-blob 直方图 | `MobileGL/MG_Util/Metrics/PipeStats.{h,cpp}` | 复用 `PayloadBucketOf` 的桶边界,**运行总量**,经 JSON dump 出口(Tracy 画不了分布,与既有 `cmd-bytes-per-draw-histogram` 的选择一致) | +| spawn server 进程从未武装计数器 | `MobileGL/MG_Remote/Server/ServerMain.cpp`,`PipeStats::Init()` 加在 `MG_ConfigLoader::Init()` 与 `MGPipeSetServerProcessRole(true)` **之后**,配一行 `Shutdown()` 在 `loop.Stop()` 之后、`_exit(0)` 之前 | **红一次**:修复前 spawn server 在 `mobilegl.server.log` 里 **0 条**汇总行,修复后 **29 条**;SSIM 1.0 不变。三处顺序都是承重的:`Init()` 从 config latch 该开关(必须晚于配置解析)、晚于定角色(让汇总行进 server 角色日志)、`Shutdown()` 晚于 `loop.Stop()`(apply 线程正是发布 `srv`/`srvpark` 的线程) | +| `MOBILEGL_PIPE_STATS_FILE` 的角色分路 | `MobileGL/MG_Util/{Metrics/PipeStats.cpp,Debug/Log.{h,cpp}}` | 走 `RoleLogPath` **同一套导出规则**派生成 `.client.json` / `.server.json`;**基名不再指向任何文件**(没改的读取方会当场 `ENOENT`,这是刻意的)。G1 上这里红过三次(无条件派生 → `.text +24 B`;两臂共用一个局部 → 1 symbol resized;重写 `MGLOG_W` 格式串 → `.rodata +64 B`),最终形态是 pull 臂的代码**逐语句原样**、所有新增都在 `#if` 之内 | +| 测试 | `MobileGL/MG_Test/Util/PipeStatsTest.cpp` 等 | PipeStats **25/25**;全量 unit **2367/2367**;spawn integration **102/102**;**G1 符号 0/0/0/0 且 `.text` 逐字节相同**(强于门要求的 `+0`),pull 构建零 `MG_Remote` 符号、零新计数器符号 | +| 设备 A/B 工具入库 | `tools/device_bench/p6/{ab_session.py,render_ab.py,README.md}` | 采集(reboot / pin / 按序跑臂 / 逐线程 CPU 采样 / 按角色拉日志 / 臂的证明)与渲染(`session-summary.json` → 表格)分离,渲染器只读采集结果,不能影响采集;`spawn` 的两个结构性零(`srv` 与 `frames` 在 server 日志)由渲染器处理并**在表里标明取自哪条日志** | +| runner 的包名覆盖 | `tools/trace_replay/run_android_retrace_local.py`(+19 行) | `MOBILEGL_TRACE_PACKAGE`;一台机器上已有另一个 key 签的 trace 安装(两个不同 key 签的 APK 不能共用一个 id),所以门 7 / 门 8 的 APK 装成 `top.mobilegl.plugin.p6gate7.trace` / `.p6gate8.trace` 并与既有安装并存,runner 的包名常量由此覆盖;既有安装原样保留 | +| `pin_device.sh` 的 `2f7cbe2e` 行 | `tools/device_bench/pin_device.sh`(+28 行) | 定频路径 + kgsl 节点 + GPU 钳 1050 MHz 的实测值(§12.7);三个动作(`check` / `pin` / `unpin`)都实跑过,pin 在负载下保持、在 idle 下 90 s 稳定,stock 值是硬编码而非现场采样(否则会把已钉的状态读成 stock 并永久钉住) | + +### 12.7 不可比项与未跑项 + +- **本板 GPU 钳 1050 MHz。** `pin_device.sh` 的 `2f7cbe2e` 行把 kgsl `min/max_pwrlevel` 钉 0,但本板实际拿到的是 **1050000000 Hz(1050 MHz)**:`thermal_pwrlevel` 恒为 1(写 0 返回成功、读回仍是 1),`max_gpuclk` 只读且为 1050000000,devfreq 的 `max_freq` 也把 1100000000 的写夹下来。所以这一行记的是「**本板真正能跑到的最高频**」,不是顶端 OPP,`check` 对着真相比对。**后果**:本节的绝对时间与 **1100 MHz 时代**的 `35d0befa` 行(§3.2 / §4.4)**不可比**,与 §7.4 那一轮 Redmi 的 1100 MHz 口径也不可比——钟频不同,**只有同场配对可比**,本节的配对结论也只在这个意义下成立。 +- **OpenRA 真机辅负载未跑。** 门 7 / 门 8 的真机会话只跑 `minecraft-1.21.4-rd12-odinlite-in-world`,OpenRA 只在 WSL 桌面 llvmpipe 的 retrace 车道跑过(§12.4 的两个数之一)。**真机 OpenRA 未跑**,如实记录。 +- **门 8① 的两种门铃只在一条负载、一台设备上比过**,且参照注记里的 1600 spins/帧 属于更重的场景;若存在差异,应在每帧会合次数更多的负载上才看得见。 +- **spawn client 的窗口化字段仍不可信**,而且这一项是**结构**不是缺陷:要让 spawn client 推进窗口,得让 client 进程到达一次 `PipeStats::OnPresent`,而 client 的帧边界在 spawn 下是 `EmitPresent`(每条 present 记录一次)——接上去会与 backend 的帧边界重复计数。**不要顺手接**。 +- **`MOBILEGL_IPC_STAGE_MB` 同时是 arena 大小与 chunk 预算的来源**,所以分块的净效果没有被单变量隔离(§12.5 的 `tex` +194%)。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P6 收官(2026-09-22)**:a6/c6/lk/so/sm/cp/hs/dl/st/t6 十包于 2026-09-21 合入 `feat/disaggregated`(头 `77cbd176`);三种传输(monolith/inproc/spawn)桌面与真机两进程 retrace 逐像素对上 golden;device-lost 闩把杀 server 从 123 s 错误 `BarrierTimeout` 变 5.7 s 正确 `DEVICE LOST`;`Session::Fail` 漏斗把裸 abort 93→4。出口门 1–6 ✅;**门 7 / 8 于 2026-09-22 采齐**:真机 reboot-clean 同热窗口配对 A/B 六臂 18/18 runner 退出 0、36/36 `PINNED`、0 `Fatal`,inproc vs spawn 的 client CPU p50 差 −0.15%(第二会话 +0.16%)——**判读 tie**(臂内散布 0.2–4.2%),monolith 基线约贵 30%;门 8 三数齐(门铃 A/B tie、`SEG_STAGE` 字节/帧、分块后记录/帧);G1 符号 0/0/0/0 且 `.text` 逐字节相同。**P6 就此收官,未设性能门**(只记录)。逐项见 `CURRENT_STAGE_PROGRESS.md`。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`a6-audit-rows.md`](a6-audit-rows.md) | a6 审计 — 全部行 | +| [`a6-audit-v1.md`](a6-audit-v1.md) | a6-audit-v1 — spawn 之前,进程/角色边界还差什么 | +| [`a6-link-experiment-data.md`](a6-link-experiment-data.md) | a6 item 5 - link experiment raw data | +| [`a6-link-experiment.md`](a6-link-experiment.md) | a6 第 5 项:server 能否不链 `MG_Impl`——实验结果 | +| [`census-classC.md`](census-classC.md) | P6 class-C dynamic census | +| [`docs-closeout-measurements.md`](docs-closeout-measurements.md) | docs-closeout-measurements — P6 收官事实写入 `MEASUREMENTS.md` | +| [`docs-closeout-progress.md`](docs-closeout-progress.md) | docs-closeout-progress — `CURRENT_STAGE_PROGRESS.md` 的 P6 收官写入报告 | +| [`docs-closeout-roadmap.md`](docs-closeout-roadmap.md) | P6 收官事实写入 `ROADMAP.md` —— 改动报告 | +| [`gate7-device-ab.md`](gate7-device-ab.md) | Gate 7 — Redmi `2f7cbe2e` paired A/B, and gate 8 item 1 (the doorbell) | +| [`gate8-doorbell-device.md`](gate8-doorbell-device.md) | Gate 8 item ① — the doorbell, measured on the device (server half included) | +| [`gate8-wire-counters.md`](gate8-wire-counters.md) | gate8-wire-counters — 门 8 的两个 wire 计数器、首采,与 spawn server 侧的 wait 账 | +| [`git-worktree-fix.md`](git-worktree-fix.md) | MobileGL-disagg worktree:git 修好报告 | +| [`lk-itemisation.md`](lk-itemisation.md) | lk — 开工前的逐文件清单 | +| [`P6-CONTRACT-DRAFT.md`](P6-CONTRACT-DRAFT.md) | CONTRACT-P6 (DRAFT) — the backend runs in a second process | +| [`P6-ENDSTATE-REVIEW.md`](P6-ENDSTATE-REVIEW.md) | P6 重审:面向 gfxstream 形状的终局 | +| [`P6-SPAWN-PLAN.md`](P6-SPAWN-PLAN.md) | P6 spawn transport — 包计划 | diff --git a/docs/Disaggregated/notes/p65/README.md b/docs/Disaggregated/notes/p65/README.md new file mode 100644 index 00000000..1bffb5e9 --- /dev/null +++ b/docs/Disaggregated/notes/p65/README.md @@ -0,0 +1,187 @@ +# P6.5 — 传输栈两轴化 + 跨机(第一波已落地,未宣布收官) + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 终局改判(2026-09-22):client 与 server 可在不同机器、不同 OS / 架构上经 TCP 连接;同机 `spawn` 保留。传输栈拆成两根独立可选、握手协商、可混搭的轴——控制面 `ITransport`(`fork` / `unix:` / `tcp://host:port`)× 数据面 `ILink`(`ShmLink` / `StreamLink`)。设计摘要见 [`ARCHITECTURE.md`](../../ARCHITECTURE.md) §11.9,契约 `MobileGL/MG_Remote/CONTRACT-P65.md`。 +- 第一波(`feat/disaggregated@fe28bdb5`,2026-09-22):全 TCP 控制 + StreamLink 数据,WSL client ↔ Redmi server。包 lk2 → wf(wire 定宽 + `wireFingerprint` + `Refuse`)→ ct(tcp 端点 + keepalive + 令牌)→ sl(`StreamLink`)→ lf(日志前送)→ sv(supervisor + 前台 Service)→ t65。 +- 验收:主机 unit 2399、integration-tcp loopback 102/102、**Redmi device-102 102/102 全 run-ahead ARMED**、代表性 retrace OpenRA 1.0;G1 0/0/0/0;真实远端 kill 133 ms、真实 Wi-Fi 中断 5053 ms 闩住 device loss。四切片代码审查无硬缺陷 + 两修复。 +- **残余(CI / 后续)**:完整 39 例 split 子集 device golden 矩阵与链路必测数(逐帧 `kWaitReply` / RTT、`SEG_STAGE` 字节/帧、`PRESENT_CREDIT` 1/2/3、loopback tcp-vs-spawn 逐线程 CPU)。第二波(同机 TCP 控制 + 共享段数据、nd `auto` / sd)未开工。 +- 证据总索引 [`evidence-index.md`](evidence-index.md)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P6.5** 传输栈两轴化 + 跨机 +- **状态**:**第一波已落地**(`feat/disaggregated@fe28bdb5`,2026-09-22):全 TCP 控制 + StreamLink 数据,WSL client ↔ Redmi server。四切片代码审查无硬缺陷 + 两修复(`AcceptPair` 重试 `EINTR`/`ECONNABORTED`、supervisor 自设 `MOBILEGL_IPC_ROLE=server`,均 `MG_Remote` 门内 G1 不动,见 [`code-review-findings.md`](code-review-findings.md));固定制品(两端 stamp `p65-acceptfix-20260922`)再验收:unit 2399、integration-tcp loopback 102、**device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED。**残余(CI/后续)**:完整 39 例 device golden 矩阵 + 必测数。设计见本表下方「立即的下一项」节 +- **落地什么 / 范围**:包:**wf** wire 定宽与布局摘要——`wireFingerprint` = `PipeFields.def` 派生的逐成员 name / `offsetof` / `sizeof` 摘要 + 有序目录摘要 + 记录头布局 + 两个 blob codec 版本 + `kOpCount` + 字节序 / 指针宽度,**保留** `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`(wire 事实)、删 `sizeof(GLFunctionsTable)`;`buildFingerprint` 只在 `Dial == Fork` 下比较;`DynamicBackendParameters` / `MGPCaps` / `RenderStateParameters` 整块 blob 的定宽化(两端是不同编译器产的不同二进制,`long` / padding / 位域差异都是静默读错状态);`Refuse{code, detail, 对端值}` 控制消息,握手路径零 abort → **ct** 控制面端点种类(`fork` 继承 fd / `unix:` / `tcp://host:port`;`DialMode{No, Fork, Connect}` 与 `MGPipeShouldDial()` 按契约 §3 补齐;TCP keepalive 失败**作为传输层报告的挂断**进 dl 的闩,不是 apply 超时)→ **nd** 数据面协商(`LinkTerms{dataPlane, delivery, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 入 Hello / Welcome,四个尺寸由 **server** 陈述;`dataPlane ∈ {SharedSegments, Stream}`;`auto` 的判据是**段实际交付成功**而不是地址判断,回落必须具名入日志与 stats;链路工厂在会话建立时选定一对,之后不变——缝是 setup-time 的,契约 §8.2)→ **sd** 共享段交付与控制面解耦(`ShmLink` 自带 AF_UNIX aux 汇合名、在 `Welcome` 里公布,`SCM_RIGHTS` 只走它;于是**控制面 TCP + 数据面共享段**在同机是合法配对;`ITransport` 收窄为纯控制帧,描述符传递归数据面)→ **sl** `StreamLink`(分块封帧;watermark 变消息且**规则逐字保留**——late-never-early、`>=` 不是 `==`、倒退是 Fatal;`appliedSeq` 修正案 + flush-on-idle;reply 变自带 seq 的消息,本链路上删 slot 池 / 取模 / stamp 自检;`SEG_STAGE` 变发送窗口,三个 cursor 要么完整复活要么不用、不留中间态;program archive 与 `draw_vbo` range 尾分片;`GetCaps` 两个 blobref 变消息;每方向每进程一条专用读线程)→ **t65** 车道。上层纯度:`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder`、`ClientSession.cpp` 的 `kSegEvent` 挂载与 `ResourceTracker.h` 的 writeback 解析都只经 `ILink`。设计 `ARCHITECTURE.md` §11.9 +- **验收门 / 证据**:门:inproc / spawn 车道结果数与逐线程 CPU 逐字不变(`ILink::ReserveRecord` 虚调用代价**实测**,`RingProducer::Reserve` 失去内联是具体风险);纯度 grep——`ShmLink` / `StreamLink` / `RingControl` / `ReplySlotPool` / `EventRingConsumer` / `m_shm.` 只出现在 `Transport/` 与其测试,上层零链路种类分支;**2×2 矩阵车道**(控制面 {unix, tcp} × 数据面 {shm, stream},loopback)名集合与 spawn 一致且同数绿,每条目记 `control=… data=…` 的 arm 证明(ID-124 的形状);跨机臂一条真 trace 逐像素;wf 三条 red-once(临时树里同宽成员互换 → 摘要变而 stamp 不变;`wireMajor = 99` → 两端具名拒绝、server exit 0、client 不回落 monolith;仅 stamp 不同的对端在 `Dial != Fork` 下连接成功——正控制);**必测数**:逐帧 `kWaitReply` 次数(`ResourceCreate` / `SetTextureParams` / 纹理半边 `ResourceSubData` 都在此列,TCP 上每次即一个 RTT)、`SEG_STAGE` 字节/帧对链路带宽、`PRESENT_CREDIT` 1..3 配对、混搭(tcp + shm)对(unix + shm)的逐线程 CPU 差 + +## 终局改判(原 `ROADMAP.md` 页首注记) + +> **终局(2026-09-22 改判)**:client 与 server 可以在**不同机器、不同 OS / 架构**上,经 **TCP** 连接;同机的 `spawn`(AF_UNIX + 共享段)保留为本机形态。传输栈拆成**两根独立可选的轴**——控制面(`ITransport`:`fork` 继承 fd / `unix:` / `tcp://host:port`)与数据面(`ILink`:共享段 `ShmLink` / 字节流 `StreamLink`)——由握手协商、可以混搭(同机可以控制面走 TCP、数据面走共享段以降开销),上层只见 `ITransport` + `ILink`,**不得按链路种类分支**。此前 [`P6-ENDSTATE-REVIEW.md`](../p6/P6-ENDSTATE-REVIEW.md) 假设的 AVF pVM / `AF_VSOCK` 终局撤回(该文顶部有更正)。这一改判把 **P6.5 从条件项变成 IPC 跑道的关键路径**,把 wire 定宽 + 布局摘要从 P7 / hs 的"以后"变成 P6.5 的第一个包,把 Ph(含配对 / 认证)变成 P12 非 loopback 监听的前提;设计见 `ARCHITECTURE.md` §11.9。 + +## 第一波验收快照(原 `CURRENT_STAGE_PROGRESS.md`,2026-09-22) + +实现、代码审查与主要正确性门已完成;**已落地并推送 `feat/disaggregated@fe28bdb5`(2026-09-22)**(原 +在 `codex/p65-all-tcp` 的 WIP + 本次审查修复 + 收尾文档一并合入)。完整 39 例 device golden 矩阵与全套 +链路必测数按用户「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**。 + +- TCP 控制、ShmLink/StreamLink 数据面、布局指纹、portable program archive v2、令牌/Refuse、 + 设备 supervisor/前台服务、日志前送与有序关闭均已接线。 +- 初始 caps 到达前误锁为 lockstep 的启动竞态已修复。READY7 主机 TCP **102/102**、 + Redmi TCP **102/102**,零 skip;两臂逐例确认 **run-ahead ARMED**,无 fallback/demotion。 + 保留车道 spawn **102/102**、inproc **186/186**,零 skip。 +- **代码审查(四并行切片:数据面 StreamLink / wire 定宽 / 控制面握手)无硬正确性缺陷**;两处修复已落地—— + `SocketTransport::AcceptPair` 重试 `EINTR`/`ECONNABORTED`(Medium,防常驻 server 因 accept 瞬态 `return 73` 整体退出)、 + `mobilegl_server_main` 自设 `MOBILEGL_IPC_ROLE=server`(Low,不再盲信 launcher);两处均在 `MG_Remote/` + (`MOBILEGL_BUILD_DISAGGREGATED` 门内),G1 monolith `.text` 构造性不受影响。其余为跟进项(`MobileGLServerService.onDestroy` + 子进程泄漏→P12、`gen_pipe.py` 裸 `char` 符号性普查无实活风险、测试缺口等),见 + [`code-review-findings.md`](code-review-findings.md)。 +- **修后固定制品再验收**(WSL client 与设备 APK 同 build stamp `p65-acceptfix-20260922`,`REQUIRE_SAME_BUILD=1` 真实通过): + 主机 full unit **2399/2399**、P6.5 子集 **147/147**、integration-tcp loopback **102/102**、 + **device-102 102/102,全 102 条 client 日志 `run-ahead ARMED`**、代表性 device retrace + OpenRA SSIM **1.0** / startup **0.999999511** / in-world **0.999995438**,三条全 **ARMED**。 +- G1 符号 **0/0/0/0**,`.text` 逐字一致;真实远端 kill **133 ms**、真实 Wi-Fi 中断 **5053 ms** 闩住 device loss。 +- 原有单角色日志读取的 CI 缺陷已与上游修复合并;模拟器掉线分类另有回归验证。 +- **残余(CI/后续)**:正式 retrace 的完整 **39 项** split 子集 device 矩阵(DirectGLES 现有阈值、OpenRA 1.0; + `ci:false` 的 rd12 单列)与链路必测数(逐帧 `kWaitReply`/RTT、`SEG_STAGE` 字节/帧、`PRESENT_CREDIT` 1/2/3、 + loopback tcp-vs-spawn 逐线程 CPU)。`minecraft-1.21.11-main-menu` 的 Redmi 32B UBO 对齐 golden 伪差为 + ci-false(设备实图 SSIM 0.998729),不作本波红。跑器 `tools/trace_replay/run_tcp_matrix.py`(支持进度超时、 + 进程组清理、实际 run-ahead 证明与可核验 checkpoint)已入库。 + +契约见 [`CONTRACT-P65.md`](../../../../MobileGL/MG_Remote/CONTRACT-P65.md), +制品身份与证据总索引见 [`evidence-index.md`](evidence-index.md); +具体结果与未决项见 [`validation-status.md`](validation-status.md)、 +[`wire-and-validation.md`](wire-and-validation.md)、 +[`performance.md`](performance.md)。以下保留 P6 收官时的原始进度快照。 + +## 第一波设计与操作手册(原 `ROADMAP.md`「立即的下一项」节) + +> 状态:**已实现并落地推送 `feat/disaggregated@fe28bdb5`(2026-09-22),代码审查完成(无硬缺陷 + 两修复),固定制品代表性再验收通过。** 主机 unit 2399、integration-tcp loopback 102、Redmi **device-102 102 全 ARMED**、代表性 retrace OpenRA 1.0 全 ARMED;G1 与真实断线门通过。完整 39 例 device golden 矩阵与余下链路必测数按「本地代表性、CI 兜底」留 CI/后续,**尚未宣布阶段收官**;逐项见 [`CURRENT_STAGE_PROGRESS.md`](../../CURRENT_STAGE_PROGRESS.md) 与 [`notes/p65/`](.)。目标形态:x86_64 的 WSL 进程作为 client,Redmi `2f7cbe2e`(aarch64,Adreno)上的 server 进程作为 server,两者在同一局域网内经 **TCP** 连接;`integration` 全部用例与 CI retrace 的 split 子集在这条链路上跑通并对上 golden。本节是 P6.5 行的第一波,只做「控制面 TCP + 数据面 stream」这一对;同机的「TCP 控制 + 共享段数据」混搭(nd `auto` / sd)是第二波,本节不做。 + +### 1 设计时的树上基线(实现前快照) + +以下表格保留本波开工前的缺口定位;当前实现与契约更正见 `MG_Remote/CONTRACT-P65.md` 和阶段进度,不以历史行号描述今天的树。 + +| 已有 | 出处 | 对本波的意义 | +|---|---|---| +| 控制面 `SocketTransport`:`Listen` / `AcceptPair` / `ConnectTo`,**汇合点是一个名字**,两条连接按序接受(第一条控制、第二条 aux);`auxFd = -1` 已是「这条链不传 fd」的诚实答案 | `SocketTransport.h:70-91` | TCP 只是第三种名字(`tcp://host:port`);两条连接的规矩原样用:第一条控制,**第二条从 aux 变成数据流** | +| 帧:`[u32 'MGLF'][u32 len][payload]`,`FrameReader` 是真正的字节流重组器 | `Framing.h` | 数据连接复用同一重组器,只换 magic | +| server 主程序:endpoint 来自 `argv[1]` / `MOBILEGL_IPC_ENDPOINT`;`Listen` → `AcceptPair` → 建 backend → `Accept`(Welcome + 七个描述符)→ 控制泵,**对端 EOF 即退出**;`MOBILEGL_IPC_DIAL=no` 反递归 | `ServerMain.cpp`(门 8 的收尾修改已落入该文件,本表不引行号) | 单会话进程形状保留为 **child**;设备侧要加一个 **supervisor** 循环 | +| client:`StartOverSocket(transport)` 已把「socket 怎么来的」与握手分开;spawn 路径 = 造 `@mgl--` 名 → `LaunchServer` → `ConnectTo` | `ClientSession.cpp:693-724` | `Dial = Connect` 只是**跳过 `LaunchServer`**、把名字换成 `tcp://` | +| 握手后 client 收 **7 个 fd**(4 段 + 3 个门铃)再 `AdoptFromDescriptors` | `ClientSession.cpp:832-897` | stream 上这一段整体不存在:段是本地私有的,门铃是本地条件变量 | +| **数据面没有缝**:`ILink` 只有声明,树里用到它的只有 `LinkProgress`(`Ring.h:97`);`ShmLink` 不存在;两端 session 直接持有 `SessionSegments`(`SessionRings.h:130`);`ServerSession.h:93` 自己写着「`lk` 把门铃挪到 `ILink` 后面并删掉这些访问器」——**没做** | `Ring.h:108`(`RingControl` 重组已做)、`ILink.h`、`StreamLink.h` | 重审 §5.1 那「约一周」的 lk 主体仍然欠着,是本波**第一个包**,没有它 `StreamLink` 无处可插 | +| wire 寻址:`MGPBlobRef{Offset, Size, Seg}` 只允许 `kSegStage`,解析集中在 `ResolveOrFatal` | `PipeWireCodec.cpp:1421`(拒绝其它段:`:496`、`:1401`) | **两端各建一个同尺寸的本地 stage 窗口、偏移一致,codec 一行不改** | +| reply:`ReplySlotHeader{Seq, Status, Size}` 16 字节、`seq % slotCount`(8 槽) | `ReplySlot.h` | 头已经是消息形状;stream 上删掉取模与 stamp 自检 | +| event:四种 kind、定宽头 | `EventRing.h` | 已是 wire 形状,原样装帧 | +| 指纹:`CapsAbiFingerprint()` = 三个 `sizeof` + 协议版本 + git stamp | `protocol.fbs` `table Hello` 的注释 | x86_64 ↔ aarch64 两份二进制:**必须换成布局摘要**,否则要么永远拒绝(stamp 不同)要么静默读错 | +| 日志按角色分文件;server 角色由启动方设的 `MOBILEGL_IPC_ROLE=server` 决定;`LogLine` 已在 `CtrlMsg` 里、**零生产者** | `ServerSpawn.cpp:116`、`ClientSession.cpp:1948` | 34 个读 `.server.log` 的消费方在跨机时读不到 → 日志前送(§3.5) | +| 车道:`mgl_itest_register_split_arms` 一个宏两条臂 {Split, Spawn},环境差只有 `MOBILEGL_TRANSPORT=spawn` + `MOBILEGL_IPC_SERVER_PATH` | `MG_IntegrationTest/CMakeLists.txt:1985-2010` | 加第三条臂 `Tcp`;名集合门(`scripts/ci/spawn_lane_parity.py`)与 arm 证明(`scripts/ci/junit_tally.py:27`)各加一臂 | +| retrace:桌面经 `MOBILEGL_TRANSPORT` / `MOBILEGL_IPC_SERVER_PATH` 环境(`run_trace_case.cmake:47-52`);设备经 `--env K=V;…` intent extra,server 路径从 `nativeLibraryDir` 解析(`TraceReplayActivity.java:374-386`);**桌面与设备共用同一套 golden**(`trace_cases.json`),驱动差异由 SSIM 阈值吸收 | — | 跨机 retrace 的 golden 就是现有的,不另造 | +| 设备已能从 app 进程 exec `libMobileGLServer.so`(spike A、`spawn-acceptance` 臂) | `apk.yml:477-487` | supervisor 就是这个可执行文件加一个 `--serve` | +| manifest **没有 `INTERNET` 权限** | `android-plugin/app/src/main/AndroidManifest.xml` | server 要监听 TCP,trace flavour 必须加 | + +### 2 拓扑:谁监听、谁拨号、为什么 + +server 在手机上**监听**,client 从 WSL **拨出**。理由不是偏好:WSL2 默认 NAT,出站到局域网直通、入站要 portproxy;手机侧无防火墙。`Role × Dial` 两轴(契约 §3)因此在本波是 `Role=Client, Dial=Connect` / `Role=Server, Dial=No`。备用形态:`adb forward tcp:40613 tcp:40613` 把 server 绑在手机 loopback、client 连 WSL 内 adb 的 loopback——**要求 adb 跑在 WSL 里**(adb-over-Wi-Fi:`adb connect :5555`),因为 Windows 侧 adb 绑的 127.0.0.1 WSL2 够不着;路由器开了 AP 隔离时用它。 + +### 3 机制 + +**3.1 端点与握手(包 ct)**。`SocketTransport::Listen/ConnectTo` 识别 `tcp://host:port`(其余仍是 AF_UNIX 路径);两条 TCP 连接按序:控制、数据。socket 选项:`TCP_NODELAY`(批处理由我们的 publish 点决定,不交给 Nagle)、`SO_KEEPALIVE` + `TCP_KEEPIDLE=2s / KEEPINTVL=1s / KEEPCNT=3` + `TCP_USER_TIMEOUT=5000ms`——Wi-Fi 掉线不产生 FIN,这是唯一能把「半开」变成挂断的机制;它以 `POLLHUP`/错误进入 `Doorbell::Dead()`,走 dl 的闩,**是传输层报告的挂断,不是 apply 超时**(开放问题 23)。`Hello` 追加 `linkTerms{dataPlane=Stream, wireForm, maxReplyBytes, cmdWindowBytes, stageWindowBytes}` 与 `token`;`Welcome` 回填 server 陈述的四个尺寸;新增 `Refuse{code, detail, 对端值}`(`CtrlMsg` 只追加)。`buildFingerprint`(git stamp)比较策略:`Dial=Fork` 不匹配 → Refuse(抓陈旧 `MOBILEGL_IPC_SERVER_PATH`,不变);`Dial=Connect` 不匹配 → **警告**,`MOBILEGL_IPC_REQUIRE_SAME_BUILD=1` 时 → Refuse(车道设它:APK 与 WSL 构建必须同一 commit)。令牌:`MOBILEGL_IPC_TOKEN` 两端一致;server 无令牌时**只允许绑 loopback**,错令牌 → Refuse 并记日志。 + +**3.2 wire 定宽与布局摘要(包 wf)**。`wireFingerprint` = `gen_pipe.py` 从 `PipeFields.def` 生成的逐成员 `{name, offsetof, sizeof}` 表的摘要 + 有序目录摘要(opcode / payload 名 / flags / WaitClass)+ 记录头布局 + `MGPipeRenderStateSpans` 的 offsetof 表 + 两个 blob codec 版本 + `kOpCount` + `MOBILEGL_ABI_VERSION` + 字节序 + 指针宽度 + `sizeof(MGPCaps)` / `sizeof(DynamicBackendParameters)`;**删** `sizeof(GLFunctionsTable)`。x86_64 ↔ aarch64 都是 LP64、小端、Itanium ABI,布局大概率一致,但**由摘要断言、不由这句话断言**;已知的真实差异是 **`char` 的符号性**(aarch64 默认无符号)——wf 普查 payload 与 blob 里裸 `char` 当数用的站点。`DynamicBackendParameters` 定宽重写(从 P7 挪来)与 `RenderStateParameters` 1168 字节整块的成员表在此包。三条 red-once 见 P6.5 行。 + +**3.3 缝的主体(包 lk2,零行为变化)**。`SessionSegments` + `RingProducer/Consumer` + `ReplySlotPool` + `EventRingProducer/Consumer` + 两个门铃搬到 `ShmLink : ILink` 后面;`SessionProducer` / `SessionConsumer` / `ServerLoop` / `PipeWireEncoder` / `ClientSession.cpp` 的 `kSegEvent` 挂载 / `ResourceTracker.h` 的 writeback 解析都改收 `ILink*`;`ServerSession.h:93` 说要删的访问器删掉。门:inproc / spawn 车道结果数与逐线程 CPU **逐字不变**(`ILink::ReserveRecord` 的虚调用在 ~850–3550 次/帧上实测;若不在噪声里,答案是把 reserve/commit 做成 link 自有 arena 上的非虚函数);纯度 grep 门上线。 + +**3.4 `StreamLink`(包 sl)——被消息复制的镜像段**。设计原则一句话:**两端各自持有一份同尺寸的私有段,链路只负责把字节搬到对面同一偏移;codec、watermark 规则、apply 线程、编码器全部不变。** +- 段:握手协商后两端各 `malloc` 四块:cmd 环(`cmdWindowBytes`)、stage 窗口(`stageWindowBytes`)、event 环、reply 邮箱。尺寸相同 ⇒ 偏移相同 ⇒ `MGPBlobRef` 与 `ResolveOrFatal` 原样工作。 +- 数据连接帧:`[u32 'MGLD'][u32 len][u8 kind][u8×3][u64 a][u64 b][payload]`。C→S:`Cmd{ringOffset, bytes}`(记录字节逐字、写到对面环的同一偏移)、`Stage{offset, bytes}`、`ClientProgress{cmdHead, submittedSeq, eventTail}`。S→C:`Progress{appliedSeq, retiredSeq, completedFrameSerial, presentAckSerial, cmdAppliedTail, eventRingFull}`、`Reply{seq, status, bytes}`(头即 `ReplySlotHeader`)、`Event{ringOffset, bytes}`。 +- client 侧:编码器照旧写本地 cmd 环与 stage 窗口;`Flush()` 把 `[上次已送, cmdHead)` 作为一帧 `Cmd` 发出(环回绕用两段 iov 一次 `sendmsg`),**之前先发本次新 stage 的 `Stage` 帧**——顺序由编码器「先 stage 后 reserve」保证;`LinkCapabilities.PublishIsDelivery=false`,所以每个等待点先 `Flush`(这个位就是为此设的)。C→S 背压:`WaitForCmdSpace` 不变(读 `Progress` 里的 `cmdAppliedTail`),`send()` 阻塞是第二层,没有无界缓冲。 +- server 侧:`mgl-srv-io` 读线程把 `Cmd`/`Stage` 拷到镜像段同偏移,再推进本地 `cmdHead`/`submittedSeq` 并敲 apply 的本地门铃;**apply 线程一行不改**。 +- `Progress` S→C 的发出时机(对 `Ring.h` 「`appliedSeq` 不得懒发布」的**修正案**,规则改成「最多晚一次 flush,绝不早」):(a) apply 线程**即将 park 前**(flush-on-idle,解决「帧内最后一条 `kWaitReply` 记录两端互等」);(b) 每次 `PostReply` 之后;(c) present ack / `completedFrameSerial` 变化时;(d) 每 64 条已 apply 记录或 1 ms 取先到者。接收侧对每个 watermark 做 `AdvanceMonotonic`,**倒退是 `Fatal{ProtocolCorruption}`**。 +- reply:`kWaitReply` 下同一时刻最多一条未决,client 侧是「单槽 + 条件变量 + seq 核对」;本链路上 slot 池、取模、stamp 自检**删除**(`ShmLink` 保留)。 +- event:server 的 `EventRingProducer` 写本地环,io 线程把新增字节装 `Event` 帧;client 读线程写进 client 本地环同偏移,`EventRingConsumer` 不变;`Drained()` 发 `ClientProgress{eventTail}`;server producer 在 `head − 对端 tail > capacity` 时**阻塞**(P5e 的规则原样,`eventRingFull` 经 `Progress` 回传)。 +- 门铃:`ConsumerBell()` / `ProducerBell()` 是本地 `CondVarDoorbell`,由各自的读线程敲;读线程见 EOF / 错误置 `Dead()`。每进程数据连接**一条**专用读线程(写在调用线程上),控制连接的读法不变。 +- 帧上限:`Cmd`/`Stage` 单帧 ≤ `kMaxFramePayloadSize`(64 MiB);一条记录本来 ≤ 4 MiB。未分片的 program archive / `draw_vbo` range 尾在 stream 上和今天一样受 `stageWindowBytes` 约束,超出仍是 `Fatal{RingOverrun}`——**不是本波阻塞项**,记在开放问题 11。 +- 不做的:`kSegShadow` / `kSegAdopt`(P8 / P11 的段)在 stream 上具名拒绝;`GetCaps` 的 blobref 若仍走 stage 之外 → 具名拒绝,本波不发明第三条载体。 + +**3.5 日志前送(包 lf)**。server 进程的日志 sink 在 `MOBILEGL_IPC_ROLE=server` 且有控制传输且 `MOBILEGL_IPC_LOG_FORWARD=1`(`Dial=Connect` 默认开)时,每行**同时**写本地 `.server.log` 并作为一帧 `LogLine`(已在 `CtrlMsg` 里)经控制连接发出;client 收到后写入**自己的** `.server.log`。于是 34 个消费方(`PipeStatsWindow` 的计数器窗口、四个 arming 场景、拒绝普查)**一字不改**。顺序:读者在 `MarkLaneLog` / `ReadLaneLogSince` 之前调一个导出的 `MGPipeSyncPeerLog()`(一次控制往返:`LogFlush` → server 冲刷并 ack),远端不存在时它是 no-op;导出符号一个,`-fvisibility=hidden` 下显式 `default`。若 lf 滑期,退路是这 34 处在 tcp 臂上**按名 SKIP**(清单文件,像 `dualblock-expected-fatals.txt`),不许静默通过。stats 行一帧一条,前送代价可忽略。 + +**3.6 设备侧 supervisor 与启动(包 sv)**。`mobilegl_server_main tcp://0.0.0.0:40613 --serve [--max-sessions 1]`:listen → 循环 { `AcceptPair`(控制 + 数据)→ `fork()` → child 关掉 listen fd、用 `SocketTransport(streamFd, dataFd)` 走今天的单会话路径到 `_exit`;parent 关掉已接受的 fd、非阻塞 `waitpid`、继续 accept }。每会话一进程 ⇒ `eglTerminate` 的进程级拆机被隔离(D9 原样);同时只服务 1 个会话,第二个连接 → `Refuse{Busy}`。启动:trace flavour 新增前台 `MobileGLServerService`(`android:process=":mglsrv"`),`am start-foreground-service` 带 `--es listen`、`--es token`、`--es env "K=V;…"`(复用 `--env` 的 `K=V;` 语法),它从 `nativeLibraryDir` exec `libMobileGLServer.so`(spike A 的路径)、给 child 设 `MOBILEGL_IPC_ROLE=server`、持 partial wake lock;`am force-stop` 整包停掉。后端由 client 的 `Hello.backendType` 请求、supervisor 的 env 可钉死,不一致 → Refuse。EGL 在 child 里是 pbuffer / surfaceless(P6 边界;真窗口归 P12)。manifest 加 `INTERNET`。 + +**3.7 谓词与测试的拓扑感知**。client 进程:`Transport=Spawn`(拓扑:两进程)、`Dial=Connect`、不 fork;`MGPipeBackendIsLocal()=false`。`CountOwnChildren()` 一类断言改为按 Dial 分支:Connect 下断言**本地零子进程** + `Welcome::serverPid` 非零 + 日志 `control=tcp`。S2(kill server)在跨机臂上经 `MGITEST_PEER_KILL_CMD` 模板(`adb -s shell run-as top.mobilegl.plugin.trace kill -9 {pid}`)真杀——**不**允许用协作式「请你退出」代替,那测的不是挂断。 + +### 4 包与顺序 + +`lk2`(缝主体,零行为)→ `wf`(定宽 + 摘要 + Refuse)→ `ct`(tcp 端点 + keepalive + 令牌 + Connect 拨号)→ `sl`(`StreamLink`,先在 **WSL loopback** 上对着 spawn 车道打平)→ `lf`(日志前送)→ `sv`(supervisor + Service + adb 脚本)→ `t65`(tcp 臂、跨机臂、门、数)。每包 red-once(R-16)。第二波(不在本节):`nd` 的 `auto` 与 `sd` 的 aux 交付(同机 TCP 控制 + 共享段数据)、P11 的 stream 具名拒绝、Windows client。 + +### 5 出口门(本波) + +1. G1 pull 0/0/0/0、`.text` 不变(全部新代码在 `MOBILEGL_BUILD_DISAGGREGATED` 下)。 +2. lk2 后 inproc / spawn 车道结果数与逐线程 CPU 逐字不变(实测,不论证)。 +3. **`integration-tcp`(WSL loopback,server 以 `--serve tcp://127.0.0.1:40613` 本地起)**:名集合 == spawn(parity 脚本加臂)、同数绿;每条目 arm 证明 `control=tcp data=stream server= pid=N`(`junit_tally --require-tcp-ran`)。 +4. **`integration-tcp-device`(server 在 Redmi)**:同一名集合;首轮**记录**、不设阻塞门,每一条与 loopback 臂的差异必须具名(GPU 能力 / P7 的 Magma 分歧 / 时限);第二轮起转硬门。 +5. **retrace**:CI split 子集(`trace_cases.json` 里未 `"split": false` 的)从 WSL 对着手机跑,DirectGLES 的 SSIM ≥ 现有阈值、OpenRA 为 1.0(E2 的锚);DirectVulkan 记录不设门(真机分歧是 P7 的)。 +6. red-once:wf 三条(P6.5 行);ct——错令牌 → Refuse、`wireMajor` 不匹配 → 两端具名拒绝、`adb shell svc wifi disable` → 10 s 内闩住(而不是 120 s `BarrierTimeout`);sl——`Progress` 倒退 → `Fatal{ProtocolCorruption}`、越窗的 `Stage` 帧 → 拒绝、去掉 flush-on-idle 后「帧内最后一条 `kWaitReply`」用例必须挂住并被超时抓到;lf——关掉前送后四个 arming 场景必须**按名红**;sv——第二个并发连接 → `Refuse{Busy}`。 +7. **必测数(记录)**:逐帧 `kWaitReply` 次数与 RTT 分布(OpenRA、MC in-world 各一);`SEG_STAGE` 字节/帧对链路实测吞吐(车道自己量一次 64 MiB `Stage` 突发);`PRESENT_CREDIT` 1 / 2 / 3 的帧率;loopback tcp+stream 对 spawn(unix+shm)的逐线程 CPU 差。 + +### 6 两边怎么配(第一波操作手册) + +**手机(server)** +```bash +# 1. 同一 commit 构建 trace APK(分离构建)并安装 +./gradlew :app:assembleTraceDebug -Pmobilegl.buildDisaggregated=ON && adb install -r app/build/outputs/apk/trace/debug/*.apk +# 2. 手机 IP +adb shell ip -f inet addr show wlan0 +# 3. 起 server(前台 Service → supervisor 监听) +adb shell am start-foreground-service -n top.mobilegl.plugin.trace/top.mobilegl.plugin.MobileGLServerService \ + --es listen tcp://0.0.0.0:40613 --es token devtoken-0123456789abcdef \ + --es env "MOBILEGL_BACKEND_TYPE=DirectGLES;MOBILEGL_PIPE_STATS=1;MOBILEGL_PIPE_STATS_PERIOD=1;MOBILEGL_LOG_FILE_PATH=/data/data/top.mobilegl.plugin.trace/files/mgl.log" +adb logcat -s MobileGL | grep -m1 'listening on tcp://' +# 停:adb shell am force-stop top.mobilegl.plugin.trace +``` + +**WSL(client)**,在分离构建目录(今天是 `~/lk-split`): +```bash +export MOBILEGL_TRANSPORT=spawn # 拓扑:两进程;不 fork,因为下面给了远端 +export MOBILEGL_IPC_CONTROL=tcp://:40613 +export MOBILEGL_IPC_DATA=stream +export MOBILEGL_IPC_TOKEN=devtoken-0123456789abcdef # ≥16 字节(PH-7 (3)):更短的令牌让 server 以 72 退出而不是监听 +export MOBILEGL_IPC_REQUIRE_SAME_BUILD=1 # APK 与本地构建必须同 commit +ctest -L integration-tcp-device -j 1 --output-on-failure # -j 1:设备同时只服务一个会话 +# retrace(同一环境): +(cd build-retrace/tools/trace_replay && ctest -V -R '^MobileGLTraceReplay\.OpenRA\.DirectGLES$') +``` +日志:client 侧 `.client.log` 与前送来的 `.server.log` 都在 WSL;设备本地副本用 `adb shell run-as top.mobilegl.plugin.trace cat files/mgl.server.log`。 + +**备用(AP 隔离 / 没有局域网直通)**:adb 跑在 WSL 里——Windows 上一次 `adb tcpip 5555`,WSL 里 `adb connect :5555 && adb forward tcp:40613 tcp:40613`;手机侧 `--es listen tcp://127.0.0.1:40613`(loopback,不需令牌),WSL 侧 `MOBILEGL_IPC_CONTROL=tcp://127.0.0.1:40613`。 + +### 7 风险与未决(本波) + +1. **`kWaitReply` × RTT**:`ResourceCreate` / `SetTextureParams` / 纹理 `ResourceSubData` 每次一个 RTT(Wi-Fi 2–5 ms);MC 加载区块的创建突发会可感知地慢。本波**只量**(门 7),减少这一列是 P9 / P10 的活。 +2. **带宽**:`SEG_STAGE` 字节/帧对 Wi-Fi;单次 128 MiB 上传 = 秒级停顿(开放问题 19、20)。 +3. **Wi-Fi AP 隔离**:用 §6 的 adb forward 备用。 +4. **DirectVulkan 真机分歧**(P7)会在门 5 出现:记录,不当本波的红。 +5. **`MOBILEGL_IPC_ROLE`**:supervisor 必须替 child 设它,否则 child 的日志会当 client 写(`Log.cpp` 从这个 env 解析角色)。 +6. **ctest 并行 vs 一设备一会话**:`-j 1` 或 supervisor `--max-sessions N`(每会话一进程,N 个 EGL 上下文同时存在,Adreno 上未验证)。 +7. **同 commit**:两端二进制不同是常态,指纹只保证 wire 布局;`REQUIRE_SAME_BUILD` 是车道纪律,不是协议要求。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **终局改判(2026-09-22)**:AVF pVM / `AF_VSOCK` 终局撤回,改为 TCP 跨机跨平台 + 控制面 × 数据面两轴传输栈;P6.5 上关键路径,Ph 前置于 P12 的非 loopback 监听;P6 核出三项漏项(server 进程 `PipeStats` 结构性零——收尾中已修、184 棘轮门、hs 的三行契约未落地)与 `CURRENT_STAGE_PROGRESS.md` 里一条错误的 S6 陈述(已改);class-C 已归零到两个(`SetSwapInterval`、`DeleteTransformFeedback`)。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`code-review-findings.md`](code-review-findings.md) | P6.5 第一波 代码审查(4 切片,2026-09-22) | +| [`data-link-implementation.md`](data-link-implementation.md) | P6.5 数据面实现记录 | +| [`evidence-index.md`](evidence-index.md) | P6.5 第一波证据索引 | +| [`mainmenu-trace-difference.md`](mainmenu-trace-difference.md) | P6.5:1.21.11 主菜单 GLES 像素差异诊断 | +| [`peer-loss-probe.md`](peer-loss-probe.md) | P6.5 真实 peer-loss 探针 | +| [`performance.md`](performance.md) | P6.5 主机逐线程 CPU 前后记录 | +| [`validation-status.md`](validation-status.md) | P6.5 TCP 车道与设备验收记录 | +| [`window-1b-p7w5.md`](window-1b-p7w5.md) | 窗口 1b · p7w5 DirectGLES × TCP 配速矩阵(中断报告) | +| [`window-1b-p7w6.md`](window-1b-p7w6.md) | 窗口 1b:p7w6 DirectGLES × TCP(2026-09-23) | +| [`wire-and-validation.md`](wire-and-validation.md) | P6.5 wire、计量与主机验证记录 | diff --git a/docs/Disaggregated/notes/p7/README.md b/docs/Disaggregated/notes/p7/README.md new file mode 100644 index 00000000..1cabd420 --- /dev/null +++ b/docs/Disaggregated/notes/p7/README.md @@ -0,0 +1,120 @@ +# P7 — DirectVulkan(Magma)全量迁移(2026-09-23 收官,ID-P7-63);Ph 必需小件 + +> 本页是该阶段的**阶段汇总**:阶段表行、实测与落地形状的完整文本(2026-09-24 从上层索引移入,原文照录)都在这里;上层只留摘要——[`ROADMAP.md`](../../ROADMAP.md)、[`MEASUREMENTS.md`](../../MEASUREMENTS.md)、[`ARCHITECTURE.md`](../../ARCHITECTURE.md)。文中 `file:line` 与"今天""当前"按各段写成时的头理解;文中的 `MEASUREMENTS.md §N` / `ARCHITECTURE.md §17.x` 编号保留,对照表见 [`../README.md`](../README.md)。 + +## 摘要 + +- 计划:[`PLAN-PH-P34B-P7.md`](PLAN-PH-P34B-P7.md)(Ph / P3b-P4b / P7 统筹,五波);规范 `MobileGL/MG_Remote/CONTRACT-P7.md`;裁定 [`INTEGRATOR-DECISIONS-P7.md`](INTEGRATOR-DECISIONS-P7.md)(ID-P7-1..63);收官交接 [`HANDOFF-2026-09-23.md`](HANDOFF-2026-09-23.md)。 +- **§8 九项全绿**:Magma 两进程车道三臂同数绿;§3.2 六个 `@P7` 退役(树上 `@P7` 拒绝站点 0);Magma `StateObjectDeathOps`;内部 shader 烘焙;OQ-8 `storageBlocks`;OQ-10 `kCapResidentSubData`;verify × split;**门 3** 真机 ssim 36/36(终局窗口 3 `p7w8-78e71f2b`);**门 5** CTS 五块 AFTER ≤ 0.5 pp。40% 检查点 6/9 已过,未触发重定基线。G1 全程恒等;棘轮 186 → 171(余 86 标 `# P13`,门 4 关闭)。 +- 关键修复:B3 找到 OpenRA 真机分歧的真因(wire 臂 completed-frame-serial floor 不健全);M2 / M3 收掉 server 端 VkBuffer 与 descriptor set 的无界累积;X1 修 arm64 persistent-map tracker 从不认领 fault。 +- **Ph**(不可信对端加固 + 配对)的必需小件随 P7 的 F / F2 包全部落地(ID-P7-57):常量时间令牌、≥16 字节、无令牌只 loopback、`Welcome.dataNonce` 数据面绑定、每会话闩、`SEG_EVENT` 弃投闩、fork 前认证 + 退避、fuzz 三臂进 CI。OQ-21 裁定令牌足够、不做 TLS。 +- 集成者审查稿在 [`../handoff/`](../handoff/)(`review-*.md`,路径被本目录交接文档引用,未移动)。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P7** DirectVulkan(Magma)全量迁移 +- **状态**:**✅ 已收官(2026-09-23,ID-P7-63)**:§8 九项全绿,终局窗口 3 门 3 36/36、门 5 PASS。**此前:wave 0–2 已落地,40% 检查点 6/9 已过(2026-09-22,`origin@b089e0de` + 集成树上的 B3)**:审计 + 五波计划见 `notes/p7/PLAN-PH-P34B-P7.md` §1.3 / §3,规范 `MG_Remote/CONTRACT-P7.md`,裁定 `notes/p7/INTEGRATOR-DECISIONS-P7.md`(ID-P7-1..33)。**wave 0** 仪器(Magma 两进程车道三臂、`link_ratchet.py` 186 基线、普查门 79 站点下行棘轮 + `MGPipeSessionFailHook`、设备跑器 `--matrix/--repeat/--archive-dir`、CTS caselists)`origin@801f8eda`;**wave 1** 设备窗口 #1(门 3 分母 36、E0–E7 归因、CTS `$BASE` 五块);**wave 2** X1(arm64 persistent-map tracker 从不认领 fault)、F1(split 起会提前)、C(`StateObjectDeathOps`、OQ-8、OQ-10、vertex-layout 拆分)、A(对齐 / native-range)、B(mip 半退役、深度 mip 烘焙 (A)(D)、copy-image、default-color-blit、回读 fence)、B2(multisample shape/aspect、AllocatorDebtScope ×2 删、(B′)、棘轮 **186→173**)、B3(**OpenRA 真机分歧的真因**:wire 臂 completed-frame-serial floor 在中帧 pooled-fence 提交退休时无证据上升 → 帧首次 `glBufferSubData` 走无序 host memcpy;计数 26→0,spawn 臂 red-once;ID-P7-33)——**树上 `@P7` 拒绝站点归零、§3.2 六退役全成、Magma 三臂 95/74/76**。**真机**:p7w4 门 3 AFTER 34/36 与 monolith 差 ≤ 0.0005;OpenRA p7w5 **27/27 golden**(ID-P7-34,项关闭);bsl-esc-menu = server 死 VkBuffer 无界累积(一帧 1.3 M 调用,ID-P7-32,**M2** 在修)。**已到集成树(2026-09-22 夜,ID-P7-37)**:B2 返工 r1 + r2、B3 返工 + 修复轮、V1、F 片 1–3 已推送 `origin@87584d0b`(2026-09-22 夜,ID-P7-39;pipe 门全绿:magma 97 / 76 / 71、full 535、verify 构建 1140 / 1074、OpenRA split 回放 0 条 unsound;四份 fable 审查 land with fixes,修复轮已落地,ID-P7-44);**M2 + M2 r2 已推送**(主机 bsl spawn server 825 → 546 MiB;memo destroy-epoch 关闭 wire store 句柄复用的 ABA;第二个泄漏 = descriptor set → M3;ID-P7-46);V1 修复 r2 已推送(read 侧负控数到 2、FATAL=0 摘要在 Close 前写出,ID-P7-47);**在跑**:F2 待派;窗口 1b 矩阵在跑(配对窗口在直连 LAN 上同样触发,ID-P7-41)。提交尾注已按用户令改写(origin `3c80cd62`)。**2026-09-23 更新(ID-P7-49–54)**:M3 已在 7ea7ce89 推送;B4 已集成于 pipe b19297d5,源码审查 land、完整集成门全绿(ID-P7-51)。p7w6 DirectGLES × TCP 38 例对照与 Photon / D24 同 APK monolith 对照已结案(ID-P7-52/53);两项错图亦在 monolith 复现。F2 PH-4/5/PH-2 已在包分支 commits `2c6a6e25` / `51480f8c` 落地并通过 Luna 审查(ID-P7-54);该矩阵不替代门 3。**CI 与门 4(ID-P7-55/56)**:CI 自 P6.5 起的七个红因已修;棘轮 171,`p7-magma` 余 86 标 `# P13`,门 4 关闭。**F2 与门 5 修复(ID-P7-57/58)**:Ph 余项与 fuzz 三臂落地;门 5 预演的三个 inproc CTS 缺陷修掉;窗口 2 工装与判读裁定(ID-P7-59)。**窗口 2 与收官审查(ID-P7-60–62)**:门 5 PASS;门 3 严格 33/36,三例 Iris monolith 同样不稳定 → 分布判据后 36/36;codex 收官审查修复、spawn 冷启动心跳(协议修订 2)、1:1 blit 最近邻落地;终局窗口 3 复跑。**余**:F2 PH-6 / PH-1 (3)(4) / PH-7 (5)、fuzz 臂 2 与完整包门;wave 3 棘轮 88;wave 4 CTS AFTER 和门 3;wave 5 fuzz 三臂与整体审查。逐日状态见 [`CURRENT_STAGE_STATUS.md`](../../CURRENT_STAGE_PROGRESS.md) +- **落地什么 / 范围**:`38919d45` 已补实际游戏的 server buffer consumers 和 Android 旋转 blit([报告](../p5f/magma-inproc-fix.md));`194382c9` 又完成 Magma run-ahead、GPU 延迟退休与 VAO 身份碰撞修复([报告](../p5f/magma-runahead.md));`ed507b1a..d56c0e83` 29 个 DirectVulkan 提交的 shape 波(随 `e1bf3677` 合入,本行此前未记)已退役 packed-float mip 原生 blit(`5310ff62`)、resolve(`ec4ca06d`)、占位图像(`WirePlaceholderImages.inc`)、copy-image 端点、XFB 捕获 span、纹理回读;`70fb6689` 链关闭 inproc RD32 性能回归(86.85→116.73 fps)。其余工作(2026-09-22 审计改判):~~`SetupDrawSnapshot` 探测字段塌成 dirty mask~~ 与 ~~`VertexInputStateFactory` 内容寻址 CSO~~ **本阶段拒做**(`SetupDraw` 在非 monolith 传输下分支到 `SetupWireDraw`,monolith 快照机制在 split 下不跑;无性能门;ID-P7-5);占位纹理原生化**已落地**(缺 split 车道条目);具名 UBO host payload(D-B8)走了直绑常驻 `VkBuffer` range 的备选,欠一次测量 + 裁定关 OQ-6;内部 shader 烘焙**半已落地**(颜色 blit `WireColorBlitSpirv.h`,仅 split 臂),欠深度 mip 烘焙 + `MOBILEGL_BAKED_INTERNAL_SHADERS` 新鲜度门(0 处);D18 按 ID-P7-6 读法一句闭。**新增必须项**:Magma `StateObjectDeathOps`(六处特判)、OQ-8 归档 `storageBlocks`(`MagmaProgramSource::EnsureStorageBlocks` 每 draw 重跑 SPIRV-Reflect)、OQ-10 `kCapResidentSubData` 从未发布(split 下双后端退回就地 memcpy)、两处 `MagmaP7AllocatorDebtScope`。**P6 真机发现(2026-09-22 归此)**:`DirectVulkan` 分离路径在 Redmi 上两次观测 0.988998 / 0.976494(inproc 与 spawn 同表现,pbuffer monolith 1.0;case 归属、重复次数未记录,原始证据不在树内;取自 run-ahead ARMED 下,**lockstep 对照从未在 golden 口径下跑过**——wave 1 的 E1)——Magma × 真实 GPU;树上 `@P7` 具名拒绝**实为 15 处 / 4 文件**(`multisample-blit-shape` / `-aspect`、`depth-stencil-mipmap`(已窄化到 D|S 合并)、`vertex-layout`、`vertex-format-conversion`、`texel-buffer-native-format`、`unaligned-{texel,storage,atomic}-buffer-range`、`storage-buffer-native-range`、`uniform-block-native-range`、`uniform-buffer-byte-tail` / `-dynamic-offset`、`copy-image-in-place`、`default-color-blit-shape`;外加 Magma 无 `StateObjectDeathOps` 与 `buffer-legacy-arm`),**全部是绕过 `Session::Fail` 与普查门的裸 abort**;必须退役 `.6/.8/.9/.11/.12/.13`,其余改 decline 即可;链接实验分给 P7 的 **101 个符号**(D12)。`DynamicBackendParameters` 定宽重写**不再是 P7 的**——它是跨平台 wire 的前提,归 P6.5 wf +- **验收门 / 证据**:集成 + trace 在 Magma 的 push 与 split 下全绿(inproc 半已是硬门;**spawn / TCP 臂今天 DirectVulkan 条目为 0**——四条 split 臂硬编码 `MOBILEGL_BACKEND_TYPE=DirectGLES`,wave 0 补后端维度);verify 零分歧(verify 今天只注册 monolith,verify+split 撞旧 `PipeRespecifyScope` 断言,先清);真机 DirectVulkan split ssim 回到 1.0(分母排除 rd12 in-world 与 create-indirect,ID-P7-4;必须带 `RUN_AHEAD=0` 对照臂);184 符号棘轮(契约 §12.1)下降 P7 的 101 个——原 "`nm -D libMobileGLServer.so | grep glslang` 为空" 不可达,D12 裁定 server 镜像就是整个 `libMobileGL.so`;仪器 `scripts/link_ratchet.py`(存符号列表、只对新增红)归 wave 0;CTS 五块(`texture_*` / `shader_image_*` / `packed_pixels` / `direct_state_access.*` / `uniform_buffer_object*`)先立 monolith×DirectVulkan 基线再比 inproc,≤0.5 pp 且新 crash = 0 单独硬红。**再基线检查点:中点完成子系统 < 40% 立即重定基线**(分母 = `notes/p7/PLAN-PH-P34B-P7.md` §4 的 9 项) + +### Ph 行(原 `ROADMAP.md`) + +- **阶段**:**Ph** 不可信对端加固 + 配对 +- **状态**:**已审计(2026-09-22,`notes/p7/PLAN-PH-P34B-P7.md` §1.1)**:P6.5 已在 LAN 上跑 `tcp://0.0.0.0`,「P12 之前」的顺序陈述落后于树。必需小件(supervisor 命名子进程死亡、两处漏斗旁路、D11 五处上限、常量时间令牌 + 数据面绑定、`SEG_EVENT` 弃投闩)随 P7 波次推进;fuzz 臂 2 在 P7 之后;OQ-21 已裁定令牌足够(ID-P7-3)。**进展(2026-09-22 深夜)**:supervisor 命名子进程死亡 + 两处头层漏斗旁路已随 wave 0 落地(`origin@801f8eda`,`abort_sites` 79 成下行棘轮);其余小件是簇 **F** 包:**片 1–3 已到集成树(2026-09-22 夜,ID-P7-37)**——令牌合一 / ≥16 字节具名拒绝 / 无令牌只 loopback / smoke 进 CI;`Welcome.dataNonce` 绑定,TCP 上 250 ms 配对窗口消失,`wireFingerprint` 变更;PH-8 钳制证明——片 4–6 与 PH-7 (5) 需要对端字节的 fuzz 驱动,另派 F2(原顺序:常量时间令牌 / `Refuse{Authentication}` / ≥16 字节 + `tcp_supervisor_smoke.py` 接 CI → `Welcome.dataNonce` 数据面绑定 → PH-8 单测 → D11 五处 + PH-2 → PH-6 drop-with-latch → PH-1 (3)(4));逐日状态见 [`CURRENT_STAGE_STATUS.md`](../../CURRENT_STAGE_PROGRESS.md)**F2 完成(2026-09-23,ID-P7-57)**:PH-1 (3)(4) 每会话闩、PH-6、PH-7 (5) fork 前认证 + 退避、fuzz 三臂进 CI——Ph 必需小件全部落地。**codex closeout(p7/int3)**:PH-5 界改为元素最小编码字节数(finding 3);`DrainRing` 在每次 pop 前查闩(finding 6:控制线程闩的窗口缩窄到查闩到 pop,未关闭)。 +- **落地什么 / 范围**:`Session::Fail` 的策略翻成每会话闩(**按 ID-P7-1 重定界**:98 处 `SessionFail` + 6 处 `WireLogFatal` 的字面翻转拒做——≥9 处返回引用没有诚实返回值;只对对端字节可达站点 latch-and-decline;P6 dl 收口到的是**两个漏斗 + 两处头层旁路**(`Server/StagedShadow.h`、`Server/StagedTextureStore.h`),不是一处;P6.5 fork-per-session 已给出「下一连接照常服务」);handle slot 预算(`BackendSlotTable` 的两条拒绝从 `MOBILEGL_ASSERT` 变 release 下的具名拒绝,杜绝静默 null twin);readback 尺寸门挪 server 侧且在 resize **之前**设绝对上限;`StagedTextureStore` 按该 level 宣告范围设界;`ArchiveVector` 按 `count × 元素最小编码字节数` 设上限(codex closeout finding 3 改自 `count * sizeof(element)`,后者拒掉编码器自己写的紧凑归档;map / set 仍按每项 1 字节);`SEG_EVENT` 溢出不再能让一个不排空(或仅涓流排空)的对端在 30 秒内、乃至瞬间用 `Fatal{EventRingOverflow}` 打掉承载它的 server 进程(drop-with-latch,不等 PH-1,ID-P7-2;原文「60 秒」是两轮 30 s 的算术上界);**配对 / 认证**(`Hello` 带令牌与无令牌只 loopback **已由 P6.5 ct 落地**;欠常量时间比较、`Refuse{Authentication}` 入词表、≥16 字节、**数据面按 `Welcome.dataNonce` 绑定到已认证的控制连接**(今天 `AcceptPair` 按到达顺序配对、从不查 peer)、fork 前认证;OQ-21 裁定令牌足够、不做 TLS);段 / 窗口尺寸由 server 钳制(P6.5 nd 已陈述,欠一条钳制单测) +- **验收门 / 证据**:非 loopback 监听在无令牌时具名拒绝(机制已在;名字入词表 + `tcp_supervisor_smoke.py` 接 CI 后算达成);畸形帧 / 超界计数 / 不排空对端的模糊臂下 server **不 abort、会话闩住、下一连接照常服务**(臂 1 机制已在、欠端到端脚本;臂 2 = PH-1 (3)(4) + D11;臂 3 = PH-6);`Session::Fail` 站点普查门扩到 server 镜像且 `abort_sites` 变棘轮(今天只扫 `MG_Remote`、站点数记了不比,wave 0);契约 §12.2 五处无界分配各一条 red-once(**五处全开**,不是两处) + +## 收官状态页(原 `CURRENT_STAGE_STATUS.md`,2026-09-23) + +**更新:2026-09-23(ID-P7-49–63)** · 交接 [notes/p7/HANDOFF-2026-09-23.md](HANDOFF-2026-09-23.md)。**P7 收官**:§8 九项全绿。CI 七根因修、F2、门 5 三缺陷、codex 收官审查修复、spawn 冷启动心跳(协议修订 2)均已落地推送。下一阶段按 ROADMAP:P8 / P9 / P12 余项。 + +### 1. 出口门总览(CONTRACT-P7 §8 的 9 项分母) + +| # | 子系统 | 状态 | 证据 | +|---|---|---|---| +| 1 | Magma 两进程车道三臂同数绿 | ✅ | M3 集成树门:magma-split/spawn/tcp **102 / 81 / 71**,full-split **540**;OpenRA DirectVulkan inproc + spawn 回放 1.000000、0 unsound | +| 2 | §3.2 六个 `@P7` 退役 | ✅ | A(byte-tail、native-range、对齐 decline)、B(copy-image、default-color-blit、mip 半退役)、B2(multisample shape/aspect)、C(vertex-layout 拆分);树上 `@P7` 拒绝站点 **0** | +| 3 | `StateObjectDeathOps` | ✅ | C,`dce9953d..cf7ca59f` | +| 4 | 烘焙 (A)(D) + (B′) | ✅ | B 深度 mip 烘焙;B2 disaggregated 构建的 monolith 臂改用烘焙模块 | +| 5 | OQ-8 反射归档 `storageBlocks` | ✅ | C | +| 6 | OQ-10 `kCapResidentSubData` 按 server 表发布 | ✅ | C | +| 7 | verify × split(门 2) | ✅ | B4 集成树 verify build:unit 2439、integration-verify 1152、integration-verify-split 1086,全绿;V1/V1 r2 负控与 8 verify trace 证据仍有效 | +| 8 | 真机 ssim 1.0(门 3,分母 36) | ✅ | 终局窗口 3(`p7w8-78e71f2b`)**36/36**:33 例四臂逐位同,三例 Iris 按 ID-P7-62 分布判据通过,OpenRA 四臂 1.000000(ID-P7-63) | +| 9 | CTS 五块 AFTER ≤ 0.5 pp(门 5) | ✅ | 窗口 2 与终局窗口 3 均 PASS:shader-image +4.35 pp、texture +0.96 pp、其余持平,0 新 crash、0 流失;UBO(GTF)记 unrun | + +**40% 检查点(§8 中点)已过:6/9**,不触发重定基线。G1 全程恒等(pull `.text` `0xa52203`、符号 0/0);census 79 站点 / 0 未标;棘轮 186 → 173 → **171**(ratchet88:`p7-magma` 余 86 全标 `# P13`,门 4 关闭,ID-P7-55)。 + +### 2. 已落地(`feat/disaggregated`,按合并序) + +| 波 / 包 | 内容 | 落地 | +|---|---|---| +| wave 0 | Magma 两进程车道、`link_ratchet.py`、普查门扩容 + `MGPipeSessionFailHook`、设备跑器 `--matrix/--repeat/--archive-dir`、CTS caselists | `origin@801f8eda` | +| wave 1 | 设备窗口 #1(Redmi 2f7cbe2e,DirectVulkan):门 3 分母 36、E0–E7 归因、CTS `$BASE` | `notes/p7/device-window-1/` | +| X1 | persistent-map tracker 在 arm64 不认领 fault(bionic 指针标记 0xb4) | `4cbf78c2` | +| F1 | split 起会提到 `MG_State::Init()` 之前 + `Fatal{CapsBeforeFirstSnapshot}` | `df17c416..3179c497` | +| C | Magma `StateObjectDeathOps`、OQ-10、vertex-layout 拆分、OQ-8 `storageBlocks` | `dce9953d..cf7ca59f` | +| A | UBO/SSBO/texel 对齐与 native-range 行 | `450d3075..5937b4fc` | +| B | mip 形状半退役、深度 mip 烘焙、copy-image、default-color-blit、wire 回读 fence | `c4be3800..76b74693` | +| D1 | XFB 两洞、回调 10→9、resolver 具名 stop、readback 三臂 | `faf0a1f9..ae3f43ab` | +| D2 + 返工 | `TextureViewAlias`、TUS 门、memo 纯度门、`SubsystemDeps.def`、场景普查 | `da30c130..519445f9`、`b3334cf3..a2d7fbd7` | +| D3 + 审查修复 | view 陈旧缺陷(clean gate 读 storage record)、D-K2 单一陈述 | `1e1f4bad..00d02396`、`22761a69` | +| B2 | multisample 两处退役、AllocatorDebtScope ×2 删、(B′)、棘轮 173 | `452d33b5..3c2867d3` | +| B3 | **OpenRA 真因**:wire 臂 completed-frame-serial floor 不健全 → 有序路径 + 可证 floor + 32 处静默出口具名 | `76bbb6c3..9a494b6f`,`origin@3c80cd62` | +| B2 返工 r1 | tcp 五条 server 端旋钮条目改 split+spawn(parity 例外 `MAGMA_SERVER_ENV_KNOB_NO_TCP`)、翻转 MS 深/模板 resolve 逐行镜像、缩放 / X 镜像 / 跨格式 decline、`MsFlip.` 用例 | pipe `9fed478d..cc3c18c5`(审查 rework → r2 在跑,ID-P7-35) | +| B3 返工 | `WireDeclines.def` 53 行全站点 + 审计脚本、`StaleSerial.` parity 例外、注记 + `:273`/`:432`、`run_trace_case.cmake` 对 `unsound-serial-complete` 打红 | pipe `b8d10d07..75a64ce0`(审查 land with fixes → 修复轮在跑,ID-P7-36) | +| V1 | `PipeRespecifyScope` 放宽、`integration-verify-split`(1074 条,双后端 inproc)、两负控 split 臂红、8 verify trace × DV × inproc 0 分歧、比对器 ReadPixels 窗口 oracle 改中性 pack | `origin@87584d0b`(审查 land with fixes → 修复轮在跑,ID-P7-37) | +| B2 返工 r2 | 用例按 `IsSplitLane()` + 后端名 skip、逐行拷贝行距对齐 + stencil 腿、`DeclineWireDepthStencilResolveShape` 先决定、`MsFlip1.`、parity tail 恰匹配 | `origin@87584d0b`(审查 land with fixes → r3 在跑,ID-P7-38) | +| B3 修复轮 | 审计剥注释 / 字面量 + 按块回溯 + `--self-test`、spawn 无 server 日志 FATAL、注记双行 + `WaitForFrameSerial` 债 | `origin@87584d0b`(审查 land with fixes → 修复 r2 在跑,ID-P7-38) | +| F 片 1–3 | 常量时间令牌 / ≥16 字节 / 无令牌只 loopback / smoke 进 CI;`Welcome.dataNonce` 绑定(TCP 250 ms 配对窗口消失,指纹变更);PH-8 钳制证明 | `origin@87584d0b`(审查 land with fixes → 修复轮在跑,ID-P7-39) | +| E1 | Iris 普查 231/231 全活、70/77 逐字节同、P9 例外表空;§7.2 同名重跑 24 绿 / 3 具名停止 / 0 错答 | `origin@87584d0b`(docs only,ID-P7-38) | +| M2 | 孤儿 wire store 在 defer 路径回收 + `MOBILEGL_IPC_WIRE_DEFERRED_MB` 水位线 + 1,024 计数上限 + `wbuf[]` 计量 + `MagmaWireReclaimScenario`;主机 bsl spawn server 825 → 546 MiB、映射 41k → 5.8k | pipe `b81a8827..7d131527`(门全绿 magma 100 / 79 / 71,审查 land with fixes,ID-P7-43);**r2** = memo destroy-epoch,must-fix 关闭:pipe `4d207468..f62703e9` + parity `1ab62ece`(门全绿 magma 101 / 80 / 71,集成者审查 land,ID-P7-46) | +| F 修复轮 | hand-off 端绑定后关 + `MSG_DONTWAIT`(多余 DataBind 具名拒绝)、listener 绑定后关、`sha256(protocol.fbs)` 钉到控制修订、定宽令牌比较、超额 Hello 记警、`MalformedHello`、文档更正 | pipe `0293aebb..9cea5140`(门全绿,集成者审查 land,ID-P7-44) | +| B3 修复 r3 + B2 r4 | 审计跨 `#endif` 只放行 disagg 守卫 + 缩进 `#else` 停走(16/16)、`EVIDENCE` 保护、tcp 信息;窗口腿独一底色、去死合取项、按符号引用 | pipe `bd849b28..4b96f55d`(门全绿,集成者审查 land,ID-P7-44) | +| M3 | 长帧 descriptor set 游标按 queue-idle 证明回卷,2049 SSBO 窗口用例 split/spawn 绿且 red-once 红;包树与 pipe 完整门绿 | pipe `9fd768d4`(ID-P7-50,集成者审查 land) | +| B4 | 聚合 submit 前缀等待、frame serial 重新扫描、Present 槽退休保护、pre-pass pending、texture prune 与 staged extent 清理 | pipe b19297d5(ID-P7-51;集成者 review land,完整 pipe 门通过;当前文档/citation/push 收尾中) | +| CI 修复 | 链接缝走工厂、TCP fixture headless + 资源锁守卫、D3 拒绝句、Magma-only decline、dual-block 登记、retrace 链(旋钮 / pull verify 日志 / spawn 冷启动预算)、并发组、80 个标题缩短 | pipe `eaef83b3..e751b07b`(ID-P7-56) | +| ratchet88 | `MGPipeTextureLegacyArmScope` 退役、余 86 标 `# P13`、棘轮 171 | `d49df354` / `d74e9923`(ID-P7-55) | +| F2 | Ph 余项:PH-1 (3)(4) 每会话闩、PH-6 事件通道弃投闩、PH-7 (5) fork 前认证 + 预认证有界 + 退避、fuzz 三臂进 CI;PH-4 两个回归(零尺寸层、`R8 == 0`)与 ID-49 回读在线内修掉 | F2 线 29 提交 + 集成 2 跟进(ID-P7-57) | +| 门 5 修复 | 超槽 ReadPixels 切带、深度/模板 resolve 臂由设备探针选(+ POST FIXED 行)、单采样深/模板 blit 改 copy、越界图像单元绑占位 | ID-P7-58 | +| 窗口 2 工装 | 门 3 / bsl / CTS AFTER 一条命令、可续跑、共享设备锁;判读按合同公式、五块必齐、同一 boot_id | ID-P7-59 | +| codex 收官修复 | 越界 / 空图像单元 nullDescriptor 或按单元私有占位、PH-5 最小编码宽度、闩 pop 前再查、探针按设备缓存、判读器硬编码 §7.2 常量 | ID-P7-60 | +| spawn 冷启动心跳 | server 执行 surface 操作期间发 `SurfaceProgress`,client 预算量沉默(封顶 120 s);控制协议修订 2 | ID-P7-61 | +| 门 3 判读 + blit | monolith 不稳定的 case 用最近邻分布判据、monolith ×5;1:1 LINEAR blit 走最近邻 | ID-P7-62 | + +### 3. 真机(Redmi 2f7cbe2e,Adreno 830) + +- 当前 APK:**p7w8**(`26.09.78e71f2-trace`,控制协议修订 2),supervisor `0.0.0.0:40613`,`/data/local/tmp/mgcts` 为 `$BASE` 库。 +- 终局窗口 3 证据 `notes/p7/device-window-2/final-p7w8/`,窗口 2 `notes/p7/device-window-2/window2-p7w7/`,三例 Iris 不确定性追查在 ID-P7-62。 + +## 里程碑记录(原 `ROADMAP.md`) + +- **P7 wave 2 落地(2026-09-22)**:X1 / F1 / C / A / B / D1 / D2 / D3 / B2 / B3 十包合入 `feat/disaggregated`(`origin@b089e0de`,B3 在集成树上待推),每包八车道全绿、G1 恒等、每包一条两进程臂 red-once、每包一次 fable 聚焦审查;子系统分母 **6/9** 于 B2 落地时越过 40% 中点,不触发重定基线。真机 p7w4:门 3 34/36 与 monolith 同;OpenRA 的两个历史点值(0.976494 / 0.988998)被证明是同一帧一次 draw 的顶点前缀撕裂——wire 臂的 frame-serial floor 不健全,B3 关闭;bsl-esc-menu 是 server 死 VkBuffer 无界累积,M2 + r2 已落地(主机 spawn server 825 → 546 MiB;真机验收待 p7w6)。 + +## 本目录 + +| 文件 | 内容 | +|---|---| +| [`device-window-1-runbook.md`](device-window-1-runbook.md) | 设备窗口 #1 运行手册(wave 1,Redmi `2f7cbe2e`) | +| [`f1-caps-placeholder.md`](f1-caps-placeholder.md) | F1 — 占位 CapsMirror 回答「server 消费这个族吗」,并丢掉整整一个族的记录(P7 wave 2) | +| [`g5-msrbo.md`](g5-msrbo.md) | P7 门 5 包 g5-msrbo:`KHR-GL46.direct_state_access.renderbuffers_storage_multisample`(inproc × DirectVulkan) | +| [`HANDOFF-2026-09-22.md`](HANDOFF-2026-09-22.md) | P7 集成交接(2026-09-22 深夜,集成者会话结束态) | +| [`HANDOFF-2026-09-23.md`](HANDOFF-2026-09-23.md) | P7 集成交接(2026-09-23 收官) | +| [`INTEGRATOR-DECISIONS-P7.md`](INTEGRATOR-DECISIONS-P7.md) | P7 集成者裁定日志 | +| [`iris-census-1135c664.md`](iris-census-1135c664.md) | Iris trace 普查 @ `3c80cd62`(P7 wave 3 包 E1,主机 lavapipe) | +| [`link-ratchet.md`](link-ratchet.md) | 链接闭包棘轮(P7 wave 0,ID-P7-7) | +| [`magma-a.md`](magma-a.md) | wave 2 包 A — Magma 的 UBO / SSBO / texel 对齐与 range 簇 | +| [`magma-b.md`](magma-b.md) | P7 wave 2-B:Magma blit / copy / mip 簇(分支 `p7/magma-b`) | +| [`magma-b2.md`](magma-b2.md) | P7 wave 2-B2:Magma blit / copy / mip 簇里包 B 没有走完的部分(分支 `p7/magma-b2`) | +| [`magma-b3.md`](magma-b3.md) | P7 wave 2-B3:Magma wire 臂的「已完成帧序号地板」不可证(分支 `p7/magma-b3`) | +| [`magma-b4.md`](magma-b4.md) | P7 B4:Magma 提交等待与长帧清理(已落地) | +| [`magma-c.md`](magma-c.md) | P7 wave 2 package C — Magma object death, resident sub-data, the reflection archive, the vertex-layout split | +| [`magma-m2.md`](magma-m2.md) | P7 wave 4 M2:Magma wire 臂的延迟 VkBuffer 集有界(分支 `p7/magma-m2`) | +| [`magma-m3.md`](magma-m3.md) | P7 M3:长帧内 descriptor set 游标回卷(草稿,待门) | +| [`magma-two-process-first-run.md`](magma-two-process-first-run.md) | P7 wave 0 包 L:Magma 两进程车道的首轮(2026-09-22) | +| [`ph-f.md`](ph-f.md) | P7 wave 2-F:Ph 小件(分支 `p7/ph-f`) | +| [`PLAN-PH-P34B-P7.md`](PLAN-PH-P34B-P7.md) | Ph / P3b-P4b / P7 统筹计划(2026-09-22,基线 `feat/disaggregated@b95f5f1a`) | +| [`verify-split.md`](verify-split.md) | P7 wave 3 · V1:出口门 2 —— verify × split(分支 `p7/verify-split`) | +| [`x1-persistent-map-segv.md`](x1-persistent-map-segv.md) | X1 — the client-thread SIGSEGV in a mapped buffer under inproc (device window #1) | +| [`x2-caps-flake.md`](x2-caps-flake.md) | X2 — the `InitialCapsStartup` unit-test flake under load | +| [`device-window-1/`](device-window-1/) | 172 个文件:设备窗口 #1(wave 1):门 3 分母 36、E0–E7 归因、CTS `$BASE`、各验证轮日志 | +| [`device-window-2/`](device-window-2/) | 51 个文件:窗口 2(`window2-p7w7/`)与终局窗口 3(`final-p7w8/`)证据、dry-run、判读工装 `tools/` | diff --git a/docs/Disaggregated/notes/p8/README.md b/docs/Disaggregated/notes/p8/README.md new file mode 100644 index 00000000..e1239b03 --- /dev/null +++ b/docs/Disaggregated/notes/p8/README.md @@ -0,0 +1,15 @@ +# P8 — emulation 下放 + 索引宿主镜像 + 协议广度(待排) + +> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。 + +## 摘要 + +- monolith 跑道,P7 之后。把读前端字节的纯 CPU 变换下放到 client(`MG_Impl/Pipe/HostResolve.cpp`:最大索引扫描、`*IndirectCount` 解析);`Server/IndexHostMirror` **待重裁**(a6 实测宿主索引 span 无 producer);multi-draw client indices、CopyImage 镜像、`generate_mipmap` CPU 回退、`texture-remint-pull` 仿真下放;大 blob carrier 只剩 program archive 与 `draw_vbo` range 尾(与 P6.5 sl 共用分片)。 +- 门:`DirectGLES.Split.*` 与 `DirectGLES.*` 逐名相同;trace split 双后端 SSIM ≥ 0.99;`create-indirect` 上 roundtrips-per-frame 读零。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P8** emulation 下放 + 索引宿主镜像 + 协议广度 +- **状态**:待排 +- **落地什么 / 范围**:`MG_Impl/Pipe/HostResolve.cpp`(~~client 数组范围~~ 已由 `beba0256` 落地为 owned buffer;最大索引扫描、`*IndirectCount` 解析,逐站点 reconcile);`Server/IndexHostMirror` **待重裁**(树里 0 处;a6 §6 实测宿主索引 span 无 producer、client 索引已是 owned buffer——按 `kCapNeedsHostIndexBytes` 的真实消费者重新裁定,不照抄 `ARCHITECTURE.md` §10.3);multi-draw client indices(`MultiDrawElements+CLIENT_INDICES` 仍具名拒绝);CopyImage 镜像搬到 client;viewport-array 回放验证;`generate_mipmap` 计划 + CPU 回退纹素;`texture-remint-pull` 仿真下放(15 处具名拒绝);大 blob carrier 只剩 **program archive 与 `draw_vbo` range 尾**(a6 §6;与 P6.5 sl 共用同一分片);`kCapDriverOrderedXfbCapture`(0 处);~~无 present fence tick~~ 只记在 P10 +- **验收门 / 证据**:`'^DirectGLES\.Split\.'` 与 `'^DirectGLES\.'` 逐名相同;trace split 双后端 SSIM ≥ 0.99 含两个 `coherent_as_flush` fixture;`ClientArrayAfterComputeWriteScenario`;`create-indirect` 上 `roundtrips-per-frame` 读零;`index-mirror-bytes` 逐用例发布 diff --git a/docs/Disaggregated/notes/p9/README.md b/docs/Disaggregated/notes/p9/README.md new file mode 100644 index 00000000..84de81dc --- /dev/null +++ b/docs/Disaggregated/notes/p9/README.md @@ -0,0 +1,15 @@ +# P9 — 反向通道(待排,P6.5 之后) + +> 尚未开工。本页保存该阶段在路线图上的完整范围与出口门(2026-09-24 从 [`ROADMAP.md`](../../ROADMAP.md) 阶段表移入,原文照录);阶段开工后,计划、裁定与报告都放在本目录。 + +## 摘要 + +- 异步 reply 语义,建立在 P6.5 的消息式 reply 之上;2 MiB reply cap 与 chunked readback(ID-47);PACK-PBO 回读 fire-and-forget(ID-57);武装剩余回调(`OnTextureWriteback`、`OnTexturePullRequest`、`OnMipLevelsGenerated`、`OnLog` 分级);`OnGlError` 有序;纹理拉取缓解 + 终止符;XFB 命名空间(`DeleteTransformFeedback`)。 +- 门:回读 / XFB 场景在 split 下绿;`TextureRemintPullScenario`;拉取计数逐 trace 发布;两条故障注入。 + +## 阶段表行(原 `ROADMAP.md`) + +- **阶段**:**P9** 反向通道 +- **状态**:待排,**P6.5 之后**(2026-09-22 重定范围) +- **落地什么 / 范围**:**异步 reply 语义**,机制建立在 P6.5 的消息式 reply 之上(不再造 `SEG_REPLY` slot 池——池今天就有,stream 链路会删它);2 MiB reply cap 与 chunked readback(契约 §10.2.3 从 P6 改判至此,ID-47);PACK-PBO 回读 fire-and-forget + client 侧 `MarkGpuWritten`(ID-57,P6 未做);十个回调里未武装的四个:`OnTextureWriteback`、`OnTexturePullRequest`、`OnMipLevelsGenerated`、`OnLog` 分级(`OnXfbScatterReady` 是零生产者、零消费者、无 EventKind 的死声明——随 P3b/P4b 删除,`kMGPipeCallbackCount` 10→9;`ClientSession.cpp:429`;`OnBufferWriteback` / `OnGpuWritten` / `OnSurfaceChanged` / `OnGlError` / `OnCapsInvalidated` 已武装);`OnGpuWritten` 收窄;`OnBufferWriteback` 批处理 + epoch 排序;`OnGlError` 有序(CONTRACT-P5C §4.2);纹理拉取四条缓解 + 终止符;XFB 命名空间(`DeleteTransformFeedback` 的 server 侧泄漏——按裁定无行、最后两个 class-C 之一)。~~`SEG_EVENT` 溢出策略~~ 机制已由 P5e 落地(`ARCHITECTURE.md` §11.7),剩余的"不排空对端杀宿主"归 Ph +- **验收门 / 证据**:回读 / XFB 场景在 split 下绿;`TextureRemintPullScenario`(含无解用例);拉取计数逐 trace 发布;两条故障注入 diff --git a/docs/Disaggregated/notes/recovered/wf2/BRIEF-DOCS.md b/docs/Disaggregated/notes/recovered/wf2/BRIEF-DOCS.md deleted file mode 100644 index 7ce50340..00000000 --- a/docs/Disaggregated/notes/recovered/wf2/BRIEF-DOCS.md +++ /dev/null @@ -1,26 +0,0 @@ -# Brief: rewrite `docs/Disaggregated/` into a clear, concise design + architecture document set - -## The user's instruction (verbatim, this is the goal) -"make documentations in `docs/Disaggregated` clear and concise. Make it just clearly describe the design and architecture, rather than decision dilemmas." - -## What exists today (branch `feat/disaggregated`, WSL tree `~/w7/pipe`, HEAD 458ccde1) -- `docs/Disaggregated/PLAN.md` (~312 KB, Chinese): the single implementation plan for MGPipe — the gallium-style explicit frontend/backend interface + backend-owned state machine + two-process split. It grew out of a design competition and three adversarial review rounds, so it is full of "v1 said X, v2 corrected to Y", "(P0 实测修正)…" patches, rejected alternatives, risk tables arguing with reviewers, and "待证/已证" bookkeeping. The design itself is settled; the prose is not. -- `docs/Disaggregated/REVIEW.md`: the design-competition and adversarial-review record. -- `scripts/check_doc_citations.py`: CI lint (job `pipe-gates` in `.github/workflows/test.yml`) that requires every `file:line` citation in `docs/Disaggregated/*.md` to resolve on the baseline commit. Run it and keep it green (read the script for its exact contract, base commit and invocation; `python3 scripts/check_doc_citations.py --help` or read the CI job). -- Code that already exists and that the docs must describe accurately (read it, do not guess): `MobileGL/MG_Pipe/` (PipeCalls.def — 71 calls after P-2 — MGPipeTypes.h, MGPipeHandles.h, MGPipeCallbacks.h, MGPipeHostSpan.h, MGPipe.h, Coverage.def, generated/), `scripts/gen_pipe.py`, `scripts/gen_pipe_dirty_surface.py`, `scripts/gen_protocol.py`, `MobileGL/MG_Remote/{Protocol,Transport}` (protocol.fbs, Ring/RingControl, Doorbell, ShmSegment, FdPassing, InProcessTransport, Framing), `MobileGL/MG_Util/Metrics/PipeStats.{h,cpp}`, ConfigLoader's `MOBILEGL_PIPE_*` knobs, `tools/spikes/{server_stub,extmem_probe}`, `MobileGL/MG_Test/Pipe`, `MobileGL/MG_Test/Wire`, `MobileGL/MG_Test/Util/PipeStatsTest.cpp`. -- Measured facts from P0 (these replace every "待证 / 设备锁 / 欠着" sentence in the current PLAN.md): read `/p0-device-findings.md` — spike A (exec a second native binary from the app's own `untrusted_app` process) is proven on both devices; spike B's external-memory tiers per device; the on-device MGPipe stats baselines (accessor calls per draw, bytes per frame per class, upload shapes, memo gates) for four traces on both backends; the harness facts (JSON dump never fires on device, `MOBILEGL_PIPE_STATS_PERIOD`, serial device runs); and that the create-indirect fixture's failure on Adreno 830 is pre-existing at `dev@81b17c0b` (verified with a baseline APK), i.e. not caused by this branch. - -## What to produce -Rewrite `docs/Disaggregated/` so a reader who has never seen the reviews can understand the design and the architecture and go implement the next phase. Chinese, same as today (technical identifiers in English). Suggested shape — adjust if the material argues for something else, but keep it small: -1. `README.md` — one page: what MGPipe is, the one-paragraph architecture (client tracker → MGPipe verbs → server object table → backend; monolith vs split build shapes), the status line (P0 landed at 458ccde1; P0.5/P1/P2 next; GO/NO-GO at day 43), and a map of the other files. -2. `ARCHITECTURE.md` — the design as decided: handles and generations; the call catalogue by group (screen / ctx-query / CSO / state / object / verb) and the record layout conventions (payload structs, var-tails, host spans, chunking bound = half the ring); render state as blob + CSO identity subset + `set_dynamic_state`; push-at-validate; texture subdata accumulation and `resource_subdata` (union box + region list); shader state = SPIR-V + reflection (the P0.5 header extraction it depends on); the reverse channel (callbacks + `resource_subdata_complete`); persistent maps and the external-memory tiers as measured; restart rewrite / multi-draw tiering on the server with the index host mirror; the transport (SPSC shm ring, RingControl cursors, doorbells, FlatBuffers control plane, SCM_RIGHTS, present credit); process/EGL/Android delivery (lib*.so executable, fork+execve, marker files); build shapes and purity gates; the verification strategy (PipeInputs strangler, `MOBILEGL_PIPE_VERIFY`, generation poison, the CI gates that exist). State each decision as a decision with its one-line reason; do NOT reproduce the dilemma, the rejected options or the review argument. Where a number justifies a decision, cite the measured number (from the findings file or PLAN.md's measured sections), not the estimate it replaced. -3. `ROADMAP.md` — phases P0…P13 as a compact table (what lands, acceptance gate, dependency), the day-43 GO/NO-GO criteria as a checklist, and the genuinely open questions as a short list (drop the ones P0 answered). -4. `MEASUREMENTS.md` (or a section) — the P0 baselines and spike results as tables, with the exact device / commit / command so they can be reproduced. -5. Delete `REVIEW.md` from this directory (the review record lives in git history: mention the commit range in README). Delete `PLAN.md` once its content is fully carried over — nothing that is still true and still needed may be lost, so do the carry-over section by section and keep a checklist in your notes; what is lost on purpose is the dilemma prose, superseded estimates, "v1/v2" archaeology and reviewer back-and-forth. -6. Every `file:line` you keep or add must be verified against the tree (open the file, check the line) and must pass `scripts/check_doc_citations.py`. Prefer citing a symbol name plus file over a raw line number when the doc will outlive the line. - -## Constraints -- Docs only. Do not touch code, scripts, workflows or tests (the citation checker's config is the one exception if a deleted file name must be dropped from it — then say so). -- Work in a git worktree: `cd ~/w7/pipe && git worktree add ~/w7/p0-docs -b p0/docs-rewrite feat/disaggregated`. Commit there in one or a few commits; do not push. Commit message: single-line subject `[Docs] (Disaggregated): …` + blank line + `- ` bullets; no Co-Authored-By. -- Long outputs go to files, never into your return value: write your carry-over checklist to `~/w7/p0-docs-notes.md` and return only the branch name, commit hashes, the doc file list with sizes, the citation-check result, and anything you deliberately dropped that a reader might miss. -- Keep it concise: the target is roughly a quarter of today's volume or less. Concise means no repetition and no argument, not omitted design. diff --git a/docs/Disaggregated/notes/recovered/wf2/BRIEF-P0.md b/docs/Disaggregated/notes/recovered/wf2/BRIEF-P0.md deleted file mode 100644 index d51e22b8..00000000 --- a/docs/Disaggregated/notes/recovered/wf2/BRIEF-P0.md +++ /dev/null @@ -1,48 +0,0 @@ -# P0 implementation brief (MobileGL disaggregation, plan "MGPipe") - -You are one implementer among several working IN PARALLEL on phase P0 of the plan in -`C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg/docs/Disaggregated/PLAN.md` -(that file is being consolidated by another agent right now; if it is missing or mid-rewrite, read -`PLAN-B-MGPipe.md` in the same directory - same content, older numbering). Read the plan's §0 (decisions D-B1..D-B8), -§4 (interface MGPipe), §6.2/§6.3 (PipeInputs, residual block), §7 (reverse channel), §8 (inherited transport design), -§10.3 (gates) and §11 P0 before touching code. Cite plan sections in your commit body. - -## Where the code lives and how you build it (WSL "Arch", 28 cores) - -- Reference tree: `~/w7/pipe` (branch `feat/disaggregated`, warm `build-linux` with clang+ccache, unit + integration tests ON, - lavapipe/llvmpipe ICD). DO NOT edit `~/w7/pipe` or `~/w7/base` - they are read-only references. -- Make YOUR OWN tree: `cp -a ~/w7/pipe ~/w7/p0-` (a few minutes; keeps the warm build dir), then work only there. - Single-writer rule: never touch another slug's tree. -- Run WSL commands from the Windows Bash tool as: - `MSYS_NO_PATHCONV=1 wsl.exe -d Arch -- bash -lc 'bash /mnt/c/.sh' 2>&1 | tr -d '\0'` - Put every non-trivial command sequence into a script file under your scratchpad directory (inline quoting through wsl.exe - gets mangled). Your scratchpad is `C:/Users/GEEKER~1/AppData/Local/Temp/claude/C--Users-geekerwan-AndroidStudioProjects-FoldCraftLauncher-MobileGL/eb1a694b-6a85-4ee7-810d-692eabf8fda6/scratchpad/wf2/p0-/` (create it; Windows path `/mnt/c/Users/geekerwan/AppData/Local/Temp/claude/...` from WSL). -- You may read/edit tree files from Windows through the UNC path `//wsl.localhost/Arch/home/swung/w7/p0-/...` with the - Read/Edit/Write tools, or edit inside WSL with python/sed from a script. Keep LF line endings. -- Build: `cd ~/w7/p0- && export CCACHE_BASEDIR=/home/swung/w7 && cmake --build build-linux -j 24 2>&1 | tail -30` - (reconfigure with the same cmake line as `~/w7/prep.sh` only if you changed CMake files: copy that line, keep `-B build-linux`). -- Tests: `cd build-linux && ctest -L unit -j 12 --output-on-failure` (428 unit tests must stay green) and, when your change - touches a backend or MG_State, `ctest -L integration-gpu -j 8` (367 scenarios; compare the pass/fail NAME set with the - baseline recorded in `~/w7/pipe-itest-baseline.txt` if present - a failure that is already in the baseline is not yours). - Per the user's rule, run REPRESENTATIVE tests locally, not everything, unless your package says otherwise. -- Android (only if your package needs it): `~/w7/pipe/build-android` is warm; NDK `/home/swung/android-sdk/ndk/27.3.13750724`, - SDK `~/android-sdk`, gradle `~/gradle-8.10.2`, JDK `/usr/lib/jvm/java-21-openjdk`. -- Devices (only if your package needs them): `35d0befa` (Xiaomi 24129PN74C, Adreno 830) and `3B159D009VZ00000` (Oppo PLG110, - Mali). Both are SHARED with another running campaign. Protocol: acquire an atomic mkdir lock BEFORE any adb use: - `LOCK="C:/Users/GEEKER~1/AppData/Local/Temp/claude/C--Users-geekerwan-AndroidStudioProjects-FoldCraftLauncher-MobileGL/8ab6b3cb-c783-4826-9fd7-39ff3498af5b/scratchpad/w7/locks/"; until mkdir "$LOCK" 2>/dev/null; do sleep 60; done; echo "p0- $(date)" > "$LOCK/owner"` ... `rm -rf "$LOCK"` when done. Hold it at most 25 minutes per acquisition. If a lock is held for more than 60 minutes (check `cat "$LOCK/owner"`), do NOT break it: report "blocked by device lock" in your result and finish the non-device parts. Always pass `-s ` to adb. Wake+unlock before launching apps (`adb -s X shell input keyevent KEYCODE_WAKEUP; input keyevent 82`). Read device logs from `/sdcard/MG/latest.log`, not logcat. - -## Rules - -- Commit on a branch named `p0/` in YOUR tree (`git checkout -b p0/`), one or a few focused commits. - Commit message format (mandatory): first line `[Type] (Scope): short lowercase description`; blank line; then `- ` bullets - with the rationale, plan section citations and evidence. Types: Feat / Fix / Test / Docs / Build. NEVER add Co-Authored-By - or any AI attribution trailer. -- Do not push anywhere. The coordinator fetches `p0/` from your tree by path. -- The default ALL target must build; unit tests must be green; no per-draw `fprintf`/`printf` instrumentation may be committed - (a CI grep gate for that is one of the P0 deliverables). -- Logging: `MGLOG_D` for non-critical lines (compiled out in INFO builds), never `MGLOG_I` for per-frame noise. -- Do not modify `3rdparty/` contents except adding the flatbuffers submodule where your package says so. -- Do not touch the fixtures under `tools/trace_replay/fixtures/` (they are real binaries in the tree; `git checkout` would - turn them into LFS pointers). -- When you are done, return: the branch name and tree path, the list of commits (hash + subject), what you tested (exact - commands and results), anything you could not finish and why, and any deviation from the plan with justification. diff --git a/docs/Disaggregated/notes/recovered/wf2/p0-device-findings.md b/docs/Disaggregated/notes/recovered/wf2/p0-device-findings.md deleted file mode 100644 index f52a3f8b..00000000 --- a/docs/Disaggregated/notes/recovered/wf2/p0-device-findings.md +++ /dev/null @@ -1,30 +0,0 @@ -# P0 device runs (2026-09-05 evening, both device locks taken over from the stale 00:39/00:43 holders) - -Tree: feat/disaggregated @ 7ef7c7e5 (aa005720 code + docs 87ee17c6 + spike-a 8a239177/38d4c237 + spike-b b82dd350/39f982e6/7ef7c7e5). -Spike-A APK: p0-spike-a-android/trace-debug-spike-on.apk (built at 30d7595b pre-rebase; identical sources). -Spike-B binary: p0-spike-b-extmem/extmem_probe (39f982e6 sources, arm64, run as `adb shell` = u:r:shell:s0). - -## Spike A — exec a second native binary from the app's own process - -| device | result | -|---|---| -| 35d0befa Xiaomi 24129PN74C, Adreno 830, Android 16 | **OK**. Parent `u:r:untrusted_app:s0:c173,c257,c512,c768` fork+execve of `/libMobileGLServer.so` → child pid 31348, exit 0, marker + stdout capture + report all present, child selinux = `u:r:untrusted_app:s0:c173,c257,c512,c768` (same domain, same categories), `execErrno=0`, **zero avc denials** in the window. | -| 3B159D009VZ00000 Oppo PLG110, Mali, Android 16 | (pending — filled below when the run returns) | - -Harness notes: the foreign-signed trace APK (versionCode 26080769, signer bb517d49, installed 2026-08-30 by the GL4.6 session) blocked `install -r`/`install -r -d` with INSTALL_FAILED_UPDATE_INCOMPATIBLE; it was uninstalled (recorded in lock-takeover-notes.txt). The env-passthrough A/B leg of 42-device.sh could not be exercised because `run-as sh -c 'cat > files/trace-replay/dummy.trace'` is refused on this ROM; the passthrough is instead proven end-to-end by the stats baseline runs (`--env MOBILEGL_PIPE_STATS=1` must produce `MGPipe stats` lines in mobilegl.log). - -## Spike B — external-memory tiers (extmem_probe, 4 MiB payload; same verdicts at 64 KiB) - -| route | Adreno 830 (35d0befa) | Mali (3B159D009VZ00000) | -|---|---|---| -| T1-opaque-fd (server exports VkDeviceMemory fd, client mmaps raw + imports) | **OK** full round trip incl. GPU touch (`/dmabuf:system`, dedicatedOnly=1, child type 4 bits 0x13) | UNSUPPORTED (`vkCreateBuffer(external)=VK_ERROR_INVALID_EXTERNAL_HANDLE`, advertisedExportable=0) | -| T1-dma-buf | UNSUPPORTED (VK_EXT_external_memory_dma_buf absent) | UNSUPPORTED (exportable=0) | -| T1-gles-memobj-fd (GL_EXT_memory_object_fd import of the export) | **FAIL**: import + glBufferStorageMemEXT accepted (GL_NO_ERROR) but every glMapBufferRange → GL_INVALID_OPERATION (persistent and plain); GL_EXT_memory_object present, GL_DEVICE_UUID unreadable | UNSUPPORTED (GL_EXT_memory_object / _fd not advertised; entry points resolve but ext strings absent) | -| T0-ahb-blob-transfer (client-allocated AHardwareBuffer BLOB → socket handoff → server Vulkan import + GL import) | **OK** full chain: cpu-lock, vk-import+map, GPU copy/fill, GL map persistent+coherent, writeback to client all byte-verified | **OK** full chain, identical verdicts (glPersistentCoherent=1, gpuRan=1) | -| T3-external-memory-host (VK_EXT_external_memory_host) | UNSUPPORTED (extension absent) | PARTIAL: import+map round-trips, but **GPU writes are not visible through the host mapping** (read-only tier) | -| T3-memfd-cross-process / client-memfd-server-import | UNSUPPORTED | OK / PARTIAL (same read-only GPU caveat) | - -**Tier decision for P11 (persistent maps across the process boundary):** the one route that is a full read/write tier on BOTH devices is **T0 — client-allocated AHardwareBuffer BLOB**, imported by the server with `VK_ANDROID_external_memory_android_hardware_buffer` (Magma) or `EGL_ANDROID_get_native_client_buffer` + `glBufferStorageExternalEXT` (Espryt), and mapped persistent+coherent on both sides. Adreno additionally offers T1 (server-exported opaque fd) for Vulkan-only paths; Mali offers nothing server-exported and its host-pointer import is read-only. Caveat: run domain was `shell`, not `untrusted_app`; the AHB socket handoff itself is exercised by every app that shares buffers with SurfaceFlinger so the domain risk is on the memfd/opaque-fd legs, not T0. - -## Stats baselines (dynamic accessor calls / draw, bytes per frame) -(pending — filled below) diff --git a/docs/Disaggregated/notes/recovered/wf3/BRIEF-P05.md b/docs/Disaggregated/notes/recovered/wf3/BRIEF-P05.md deleted file mode 100644 index 315ce4df..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/BRIEF-P05.md +++ /dev/null @@ -1,434 +0,0 @@ -# P0.5 implementation brief — value header + artifacts header + include-closure gate - -Tree: `feat/disaggregated @ 6672778b` (worktree `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg`; WSL `~/w7/pipe` and the three package worktrees `~/w7/p05-value-types` / `~/w7/p05-program-artifacts` / `~/w7/p05-include-gate`, branches `p05/`, all at `6672778b`, all with a configured `build-linux/` — Release, `/usr/sbin/clang++` (clang 22; there is no `clang++-20` in WSL), ccache, `MOBILEGL_BUILD_TEST=ON`, `MOBILEGL_BUILD_INTEGRATION_TEST=ON`, `MOBILEGL_BUILD_DISAGGREGATED=OFF`, log level INFO). -Every `file:line` below was re-opened at `6672778b`; the scouts were written at `6e0e3df3` and all their line numbers still hold. Corrections to the scouts are marked **[correction]**. - ---- - -## A. Goal and acceptance gate (verbatim) - -`docs/Disaggregated/ROADMAP.md:16`: - -> **P0.5** 值头与制品头抽取 | 6–9 | `MG_Pipe/MGPipeValueTypes.h`(`RenderStateParameters`、`SamplerParameters`、`PixelStoreParameters`、`VertexAttribute`… 不 include `MG_State/GLState`);`MG_State/GLState/ProgramState/ProgramArtifacts.h`(五个反射类型,不 include `ShaderObject.h`/`SpvcSession.h`,更新 7 个 includer);`Visit()` 归档 + `sizeof` 绊线;CI `-H` include 闭包断言 | **全套测试逐名不变(纯搬移);两条闭包断言绿且人为加回一个 `MG_State` include 能变红;`nm`/`.text` 变化可逐符号归因** | P0;**P1 与 P7 的硬前置** - -`docs/Disaggregated/ARCHITECTURE.md:260` is the manifest: value header = `MAX_DRAW_BUFFERS`, `PerBufferBlendState`, `StencilFaceState`, `PixelStoreParameters`, `RenderStateParameters`, `SamplerParameters`, `BorderColorForm`, `VertexAttribute`, `VertexBufferBindingPoint`, "它不 include `MG_State/GLState` 任何东西"; artifacts header = `TypeFacts`, `ResourceReflection`, `XfbVarying`, `LinkArtifacts`, `SpirvArtifacts`, "只 include `` 与容器". `ARCHITECTURE.md:259`: archive = `Visit()` + `static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE)`, one field table for both directions. `ROADMAP.md:7`: **每个门必须能因它存在的理由变红**; Windows 机器不是正确性门. `ROADMAP.md:34`: P0.5 is one of the five boundaries that owes a full `gl44to46` caselist run (CI/device, off the critical path). - -Decoded: (1) no behaviour change, every existing test name still present and green (adding tests is fine); (2) two closure probes green + a negative control that goes red; (3) every `nm --defined-only -S` delta on `libMobileGL.so` explainable per symbol. - ---- - -## B. Package split - -### B.0 Decisions shared by all three packages (fixed here; do not re-decide inside a package) - -| # | decision | why | -|---|---|---| -| D1 | **`DynamicBackendParameters` does NOT move.** `MGPipeTypes.h:32` keeps `#include `; the compositional assertion `MGPipeTypes.h:144-145` stays. | Not in the manifest; it has `SizeT` fields (`BackendObject.h:317,322,545`) and two member functions taking `TextureTarget` (`:487-497`) that force `TextureEnum.h`; "fixed-width members" is a type change, not a move. Escalate to the plan owner as a P1/P7 item. Consequence: probe A asserts `MGPipeValueTypes.h`, not `MGPipeTypes.h`. | -| D2 | **All eleven enums `RenderState.h:15-189` move**, including `StencilFace`, `PixelStoreParam`, `CapabilityInput`. | A split (some enums here, some there) is the maintenance trap; namespace `MobileGL` is unchanged so no spelling changes anywhere. | -| D3 | **`VertexAttribute`, `VertexBufferBindingPoint`, `VertexAttributeVersion` keep namespace `MobileGL::MG_State::GLState`** (the value header reopens it and forward-declares `class BufferObject`). **[correction** to scout-value-types §7.2, which recommended re-homing them in `MobileGL` + a `using`]. | Zero mangled-name churn for the value-types package, so the symbol report attributes 100 % of P0.5 churn to the artifacts de-nesting. Gate A is a path check; namespaces are irrelevant to it. Re-homing is a P13 cosmetic. | -| D4 | `MAX_DRAW_BUFFERS` becomes `inline constexpr Uint kMGMaxDrawBuffers = 8;` in `namespace MobileGL` (value header); `FramebufferObject::MAX_DRAW_BUFFERS` is **defined from it** (`= MobileGL::kMGMaxDrawBuffers`), so the 80 existing spellings keep working and cannot drift. `RenderStateParameters::MAX_VIEWPORTS` moves with its struct unchanged. No other new constants (**[correction]**: scout-value-types §6 `kMGMaxViewports`/`kMGMaxVertexAttribs*` are dropped). | Single definition; no `static_assert`-in-a-.cpp indirection. | -| D5 | **`LinkArtifacts::program` (`SharedPtr`) and `uniformInitialValues` (`Vector`) move VERBATIM.** No `UniformInitializer` replacement in P0.5 (**[correction]** to scout-program-artifacts §6.2 item 5; recorded as the P7 follow-up). | Pure move. Both are symbol-free: `shared_ptr`'s deleter is type-erased at construction (only `ProgramLinkTask.cpp` constructs one) and `TUniformInitializer` (`include/glslang/MachineIndependent/localintermediate.h:627-636`) is an aggregate of `std::string`/`std::vector`/an unscoped enum with implicit inline special members. `:80-84` makes both types complete on every side anyway. The gate pins the count of `glslang::` tokens in the header at exactly 2 so nothing new sneaks in. | -| D6 | `ProgramArtifacts.h` includes exactly `` and `` (`std::set` at `ProgramObject.h:1331,1336`; `` is in no header on the path today). | `ARCHITECTURE.md:260`. | -| D7 | `sizeof` tripwires: exact literal for POD types on every ABI (`TypeFacts` 44, `PixelStoreParameters` 28, `PerBufferBlendState` 28, `StencilFaceState` 28, `RenderStateParameters` 1168 — `MEASUREMENTS.md:86`, `ARCHITECTURE.md:188`, consistent with `MGL_RESIDUAL_BLOCK_SIZE 1248` at `MGPipeTypes.h:535`); **per-STL** numbers for container-bearing structs, asserted only under `__GLIBCXX__ && !_GLIBCXX_DEBUG` (the CI toolchain) with a `_LIBCPP_VERSION` (NDK) branch pinned by the integrator; MSVC unasserted. | `sizeof(std::string/std::set)` differs between libstdc++ and libc++ (`ARCHITECTURE.md:259` assumes one number; it cannot be one number). | -| D8 | **No `PipeFields.def` field lists for the value types in P0.5** (they need `std::array` support in `gen_pipe.py`'s comparator; `std::array::operator==` is unconstrained, so the `requires (x == y)` branch in `PipeVerify.inc:230` would hard-error); only the two comments that promise them "in P0.5" (`scripts/gen_pipe.py:163-165`, generated into `generated/PipeVerify.inc:232-238`) are re-worded to say P1. | Pure move; `MOBILEGL_PIPE_VERIFY` is not live until P1. | -| D9 | Gate skip semantics: a probe whose header does not exist prints `SKIP` and is counted; `--require-all` turns a SKIP into a failure. The negative control does not depend on either new header. CI and the ctest run **without** `--require-all` until the integrator flips it after all three packages land. | The task's "tolerate a not-yet-existing header" requirement, reconciled with `ROADMAP.md:7` (an all-skip run must not be a pass once the headers exist). | -| D10 | `.github/workflows/test.yml:3-8` triggers are NOT changed (`feat/disaggregated` is not a push trigger). The integrator runs `gh workflow run test.yml --ref feat/disaggregated` and records the run URL. | Adding the branch to the trigger list also fires the retrace jobs on every push; that is the plan owner's call, not this phase's. | - -**Header paths and probe TUs (fixed contract; the gate package codes against these before the other two land):** - -| probe | header (repo-relative) | probe TU text (exact) | forbidden path prefixes (repo-relative, after `normpath`) | -|---|---|---|---| -| `value-header` | `MobileGL/MG_Pipe/MGPipeValueTypes.h` | `#include \n` | `MobileGL/MG_State/`, `MobileGL/MG_Impl/`, `MobileGL/MG_Backend/`, `MobileGL/MG_Remote/` | -| `artifacts-header` | `MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` | `#include \n` | `MobileGL/MG_State/GLState/ProgramState/ShaderObject.h`, `MobileGL/MG_Util/ShaderTranspiler/`, `MobileGL/Config.h`, `MobileGL/MG_Backend/`, `MobileGL/MG_State/GLState/BufferState/`, `MobileGL/MG_State/GLState/ProgramState/Shader` (prefix; catches `ShaderStage.h`, `ShaderCompileTask.h`, `ShaderCompileAdoptionMap.h`, `ShaderPreprocessCache.h`, `ShaderSourceKey.h`); text limit: `glslang::` ≤ 2 occurrences | -| `wire-header` (bonus, green today) | `MobileGL/MG_Remote/Transport/ITransport.h` | `#include \n` | `MobileGL/Includes.h` (`WireLog.h:9-24` states the rule; `ITransport.h:33,35` include only `../Protocol/mg_protocol_base.h` and ``) | - -The forbidden sets deliberately exclude glslang / spirv-cross / vulkan: `MobileGL/Includes.h:53,56,59,80-84,130` pulls `ska`, `xxhash`, `spirv_cross_c.h`, five glslang headers and `vulkan.h` unconditionally, and both new headers are allowed ``. A "no glslang in the closure" assertion is unsatisfiable by construction; P7's criterion is `nm -D | grep glslang` on the server binary, a symbol gate. - ---- - -### B.1 Package `p05-value-types` (tree `~/w7/p05-value-types`, branch `p05/value-types`) - -**Files** - -| action | file | what | -|---|---|---| -| CREATE | `MobileGL/MG_Pipe/MGPipeValueTypes.h` | the value header (layout below) | -| MODIFY | `MobileGL/MG_State/GLState/RenderState/RenderState.h` | replace `:11-12` with `#include `; delete `:15-370` (keep `:14 namespace MobileGL {`); `namespace MG_State { namespace GLState { class RenderState … } }` (`:372-538`) untouched | -| MODIFY | `MobileGL/MG_State/GLState/RenderState/RenderState.cpp` | add `#include ` after `:9` — **[correction]**: no scout saw that `RenderState.cpp:460,488,524,542,572,586` spell `MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS` while the .cpp includes only `RenderState.h`, `Log.h`, `Types.h` (`:9-11`) | -| MODIFY | `MobileGL/MG_State/GLState/SamplerState/SamplerObject.h` | replace `:11` (`VectorTypes.h`) with `#include `; delete `:14-97` (keep `:13 namespace MobileGL {`; `class SamplerObject` from `:99` unchanged — it uses `SamplerParameters`, `FloatVec4/IntVec4/UintVec4`, `BorderColorForm`, all now supplied by the value header) | -| MODIFY | `MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h` | add `#include ` after `:12`; delete `:17-70` (`VertexAttribute`, `VertexBufferBindingPoint`, `VertexAttributeVersion`); keep `:11 "../BufferState/BufferObject.h"` (the class at `:72+` uses `BufferObject` 6 times) | -| MODIFY | `MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h` | add `#include ` after `:13`; `:106` → `static constexpr Uint MAX_DRAW_BUFFERS = MobileGL::kMGMaxDrawBuffers;` | -| MODIFY | `MobileGL/MG_Pipe/MGPipeTypes.h` | `:33` → `#include "MGPipeValueTypes.h"`; keep `:32` (D1); rewrite the debt comment `:24-31`: the `MG_State` half is repaid, `MGPCaps`/`DynamicBackendParameters` remains (D1) and is what still keeps gate A off `MGPipeTypes.h`; `:36-40` `using` lines unchanged except the comment `:37-38` ("now live in MGPipeValueTypes.h") | -| MODIFY | `scripts/gen_pipe.py:163-165` + regenerate `MobileGL/MG_Pipe/generated/PipeVerify.inc` | comment only: "field lists of their own in P1 (P0.5 moved the types)"; run `python3 scripts/gen_pipe.py` and commit the regenerated `.inc` (CI `pipe-gates` diffs it, `test.yml:819-827`) | -| MODIFY | `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp` | add the tests in "Tests" below; **must not gain any include** (`:17-18` are `"Includes.h"` + ``; adding `RenderState.h` there would defeat the gate) | -| MODIFY (only if the build demands) | any `.cpp` that relied on `RenderState.h` transitively supplying `FramebufferObject.h`/`TextureObject.h`/`RenderbufferObject.h`/`SamplerObject.h` | add the direct include **to that .cpp**, immediately after its existing `MG_State/GLState/...` include; never re-add anything to `RenderState.h`. Candidates: the TUs including the four `MG_Util/Converters/*/RenderStateEnumConverter.h` or `MG_Util/Texture/PixelStoreProcessor.h` (`DirectGLES.cpp`, `VulkanRenderer.cpp`, `GL_Buffer.cpp`, `GL_Getter.cpp`, `GL_RenderState.cpp`, `GL_Texture.cpp`, `TextureTest.cpp`) — all also include `Core.h`, whose `:20 FramebufferState.h:12` keeps `FramebufferObject.h`, so most likely none. List every such file in the commit message. | - -**`MGPipeValueTypes.h` layout** (one `namespace MobileGL` block, dependency order; every moved region is a verbatim cut, comments included): - -``` -// banner: copy MGPipeTypes.h:1-7, path = MobileGL/MG_Pipe/MGPipeValueTypes.h -#pragma once -#ifndef MOBILEGL_MG_PIPE_VALUE_TYPES_H // belt and braces: this file is reachable both as -#define MOBILEGL_MG_PIPE_VALUE_TYPES_H // and <…> (CMakeLists.txt:531,535) -#include -#include // includes only + (VectorTypes.h:11,13) -#include // offsetof -#include -// PURITY: nothing from MG_State/, MG_Impl/, MG_Backend/, MG_Remote/ — scripts/check_include_closure.py -// probe "value-header" (ROADMAP P0.5; ARCHITECTURE.md:260, :501 gate A). Adding one turns CI red. -namespace MobileGL { - // GL_MAX_DRAW_BUFFERS as MobileGL advertises it. FramebufferObject::MAX_DRAW_BUFFERS is defined - // from this constant, so the two cannot drift. - inline constexpr Uint kMGMaxDrawBuffers = 8; - - // RenderState.h:15-189 verbatim (11 enums, D2) - // RenderState.h:191-370 verbatim, with :263 and :273 respelled - // Array BlendStates; - // Array ColorMasks; - // MEMBER ORDER UNCHANGED (DirectGLES.cpp:2070-2081 offsetof spans; PipeSpanTable.inc:34-59 names 24 members) - // SamplerObject.h:14-97 verbatim (6 enums incl. BorderColorForm : Uint8, SamplerParameters) - - namespace MG_State::GLState { - class BufferObject; // BufferObject.h:61 forward-declares it the same way - // VertexArrayObject.h:17-70 verbatim: VertexAttribute, VertexBufferBindingPoint, VertexAttributeVersion - // (SharedPtr members: NOT trivially copyable by design; MGPipeTypes.h:247-253 carries them as a blob) - } - - // ---- trip wires (new; none existed for these before) ---- - static_assert(std::is_trivially_copyable_v && sizeof(PixelStoreParameters) == 28); - static_assert(std::is_trivially_copyable_v && sizeof(PerBufferBlendState) == 28); - static_assert(std::is_trivially_copyable_v && sizeof(StencilFaceState) == 28); - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_standard_layout_v); // offsetof legality - static_assert(sizeof(RenderStateParameters) == 1168, "RenderStateParameters changed size; MGL_RESIDUAL_BLOCK_SIZE and the Espryt spans depend on it"); - static_assert(offsetof(RenderStateParameters, BlendStates) < offsetof(RenderStateParameters, LogicOp)); - static_assert(std::tuple_size_v == kMGMaxDrawBuffers); - static_assert(std::is_trivially_copyable_v && sizeof(SamplerParameters) == /*measure: expected 100*/); - static_assert(std::is_trivially_copyable_v && sizeof(MG_State::GLState::VertexAttributeVersion) == 6); -} // namespace MobileGL -#endif -``` -Measure a number before pinning it: compile once with `== 1`; clang/gcc print `note: expression evaluates to 'N == 1'`. If `is_standard_layout_v` fails (it should not: all members public, `Vec4` adds no data to `VecBase`, `VectorTypes.h:17,161`), drop that one assertion and say so in the commit — do not touch the vector types. - -**Ordered steps** -1. Create the header by cutting the regions above (cut, never copy; the ODR grep in step 6 proves it). -2. Apply the five `MG_State` header edits and the `RenderState.cpp` include. -3. Apply the `MGPipeTypes.h` edit; `gen_pipe.py` comment + regenerate. -4. Build: `cmake --build ~/w7/p05-value-types/build-linux --parallel $(nproc)`. Resolve any lost-transitive-include failure by adding the direct include at the consumer (table above). -5. Add the tests; rebuild; run. -6. Verification commands (below) all green; commit as ONE commit: `[Refactor] (Pipe, State): extract MGPipeValueTypes.h - move the render-state, sampler and vertex value types and their enums out of MG_State/GLState so MG_Pipe no longer reaches RenderState.h; pure move, member order and namespaces unchanged`. - -**Constraints** -- Byte-for-byte identical behaviour: no member reorder (Espryt spans `DirectGLES.cpp:2070-2081, 2660-2683`; `PipeSpanTable.inc:34-59` names 24 members by string for P2's `offsetof` table), no enum underlying-type change (`BorderColorForm : Uint8` is load-bearing for `SamplerParameters` tail padding), no `SizeT`→fixed-width widening, no renames, no namespace changes (D3), no reformatting beyond the re-indentation the cut itself needs (do not run `scripts/format_code.sh` over the touched files unless it is a no-op on the untouched ones). -- `MobileGL/MG_Backend/**` is not touched: all 51 `RenderStateParameters` uses in `DirectGLES.cpp`, the 17 in `VulkanRenderer.cpp`, the `Managers.h:1087,1825` `SamplerParameters` shadows, `Managers.h:744,788` `VertexAttribute`/`VertexAttributeVersion` compile unchanged (P1's `sed` counts at `ARCHITECTURE.md:344` depend on this — verify `grep -rc "pGLContext" MobileGL/MG_Backend | sort` is identical before/after). -- No `#include ` or `` in the new header, ever; MG_Pipe never includes MG_State back. -- Existing test names unchanged; only additions. - -**Tests to add** (`MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp`, `TEST(PipeCatalogue, …)`, no new includes): -- `ValueTypeLayoutsArePinned`: runtime `EXPECT_EQ` twins of the `sizeof`/`offsetof` static assertions above (so the numbers show up in `ctest` output on every platform, including ones where a static assertion is skipped). -- `ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail`: `offsetof(ResidualValueBlock, Pack) == sizeof(RenderStateParameters)` and `offsetof(ResidualValueBlock, CapabilityBits) == 1200` — pins that the move did not alter the carrier. - -**Verification (WSL, in `~/w7/p05-value-types`)** -``` -cmake --build build-linux --parallel $(nproc) -ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -ctest --test-dir build-linux -N | grep -E '^\s+Test #' | sed 's/^ *Test *#[0-9]*: //' | sort > /tmp/vt-after.txt -ctest --test-dir ~/w7/pipe/build-linux -N | grep -E '^\s+Test #' | sed 's/^ *Test *#[0-9]*: //' | sort > /tmp/vt-before.txt -comm -23 /tmp/vt-before.txt /tmp/vt-after.txt # must print nothing (no test name lost) -# ODR: exactly one definition each, all in the new header -grep -rn "^\s*struct \(RenderStateParameters\|PixelStoreParameters\|PerBufferBlendState\|StencilFaceState\|SamplerParameters\|VertexAttribute\|VertexBufferBindingPoint\|VertexAttributeVersion\)\b\|^\s*enum class \(BlendFactor\|BorderColorForm\|CapabilityInput\)\b" MobileGL -# purity (text) -grep -n "MG_State/\|MG_Backend/\|MG_Impl/\|MG_Remote/" MobileGL/MG_Pipe/MGPipeValueTypes.h # empty -grep -n "#include" MobileGL/MG_State/GLState/RenderState/RenderState.h # Includes.h + MGPipeValueTypes.h only -# closure (compiler; header must be self-contained) -printf '#include \n' > /tmp/probe_vt.cpp -clang++ -std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -H -E -o /dev/null /tmp/probe_vt.cpp 2>&1 | grep -E '^\.+ ' | grep -c 'MobileGL/MG_State/\|MobileGL/MG_Backend/' # 0 -clang++ -std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -fsyntax-only /tmp/probe_vt.cpp -# once p05/include-gate exists (rebase onto it, or copy the script in): python3 scripts/check_include_closure.py --mode both --probe value-header -# backend read sites untouched -git diff --stat 6672778b -- MobileGL/MG_Backend # empty -# symbol report expectation for this package alone: 0 added / 0 removed / 0 resized / 0 renamed (see B.3 symbol_report.py) -python3 scripts/symbol_report.py --before ~/w7/pipe/build-linux/libMobileGL.so --after build-linux/libMobileGL.so -python3 scripts/gen_pipe.py --check -``` - ---- - -### B.2 Package `p05-program-artifacts` (tree `~/w7/p05-program-artifacts`, branch `p05/program-artifacts`) - -**Files** - -| action | file | what | -|---|---|---| -| CREATE | `MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` | the five types + `kInvalidUniformOffset` + `VisitFields` tables + tripwires (layout below) | -| MODIFY | `MobileGL/MG_State/GLState/ProgramState/ProgramObject.h` | commit 1: add `#include "ProgramArtifacts.h"` after `:11`; delete `:41-104` and insert the alias block; `:545-547` → `static constexpr Uint kInvalidUniformOffset = MobileGL::MG_State::GLState::kInvalidUniformOffset;` (keep the comment); delete `:1144-1171` (XfbVarying + its 2-line comment) and `:1173-1449` (the LinkArtifacts comment block, `LinkArtifacts`, the SpirvArtifacts comment block, `SpirvArtifacts`). Commit 2: delete `:14 #include ` (the header names no `spvc_*`/`SpvReflect*`/`SpvcMetadata` symbol — verified by grep, the include line is the only match) | -| CREATE | `MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp` | tests below | -| MODIFY | `MobileGL/MG_Test/Program/CMakeLists.txt` | append a `ProgramArtifactsTest` stanza copied from the complete `AsyncCompileTest` stanza (`add_executable`, `target_include_directories` with `${MGL_ROOT}/include ${MGL_ROOT}/MobileGL`, `target_link_libraries(... GTest::gtest_main ${LINK_LIBRARIES})`, its `gtest_discover_tests(... DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)`) | -| MODIFY (commit 2, only if the build demands) | any `.cpp` that got ``/`SpvcSession.h` transitively via `ProgramObject.h` | add `#include ` **immediately after that TU's `ProgramObject.h` include line**. Candidates: `UniformManager.cpp`, `VulkanRenderer.cpp`, `ProgramInterface.cpp`, `ProgramFactory.h`, `ProgramTranslationCache.h/.cpp`, `ProgramLinkTask.h/.cpp`, `ProgramObject.cpp` (the other 17 `SpvcSession.h` includers already include it themselves) | - -Includers of `ProgramObject.h` **[correction]**: 8 non-self files, in two spellings — `MG_Backend/DirectVulkan/Renderer/ProgramFactory.h:13`, `UniformManager.cpp:13`, `VulkanRenderer.cpp:17`, `MG_Impl/GLImpl/Program/ProgramInterface.cpp:11`, `ProgramLinkTask.h:11`, `ProgramTranslationCache.h:11` (as ``), `ProgramPipelineObject.h:11`, `ProgramState.h:12` (as `"ProgramObject.h"`), plus `ProgramObject.cpp:9`. The roadmap's "7" is `ShaderObject.h`'s count. **None of the 8 needs an edit**: the in-class aliases keep every spelling (`ProgramObject::LinkArtifacts`, `ProgramObject::TypeFacts`, `ProgramObject::kInvalidUniformOffset` at `VulkanRenderer.cpp:4659,8553`, `GL_Program.cpp:1249,1304,1522`, `Core.cpp:513-514`, `ProgramSpirvTask.cpp:353,357,454`, the tests) compiling unchanged. Narrowing `ProgramTranslationCache.h`/`ProgramInterface.cpp` to the new header is a P7 follow-up, not P0.5. - -**`ProgramArtifacts.h` layout** - -``` -// banner (copy from ProgramObject.h:1-7, path adjusted) -#pragma once -#include // String/Vector/Array/UnorderedMap/SharedPtr + GL enums. DEBT NOTE (MGPipeTypes.h:24-31 style): - // Includes.h:80-84 still pulls glslang and :59 spirv_cross_c.h; this header is glslang-free BY SYMBOL - // (what P7's `nm -D | grep glslang` measures), not by preprocessed text. A textually glslang-free - // closure needs MG_Util/Types.h split off Includes.h (Types.h:11 includes it back) - out of scope. -#include // std::set (LinkArtifacts); NOT provided by Includes.h -// PURITY: no ShaderObject.h, no SpvcSession.h, nothing under MG_Util/ShaderTranspiler/, no Config.h, no MG_Backend/. -// scripts/check_include_closure.py probe "artifacts-header". `glslang::` appears exactly twice below (D5) and the gate pins that. - -namespace MobileGL::MG_State::GLState { - // ProgramObject.h:545-547 -> namespace scope (SpirvArtifacts::reservedNumSamplesOffset defaults to it) - inline constexpr Uint kInvalidUniformOffset = ~0u; - - // ProgramObject.h:41-72 verbatim struct TypeFacts - // ProgramObject.h:74-99 verbatim struct ResourceReflection - // ProgramObject.h:101-104 verbatim using UniformReflection/BlockReflection/PipeInputReflection/PipeOutputReflection - // ProgramObject.h:1144-1171 verbatim struct XfbVarying - // ProgramObject.h:1173-1393 verbatim (comment block + struct LinkArtifacts; the I5 paragraph :1184-1188 gets one added - // sentence: "m_artifacts lives in ProgramObject and is private there; the type being - // namespace-scope changes nothing about that gate") - // ProgramObject.h:1395-1449 verbatim (comment block + struct SpirvArtifacts) - - // ---- the archive field tables (ARCHITECTURE.md:259): ONE table per type, serving both directions ---- - // Visitor contract: v(const char* name, Field&). A visitor recurses into TypeFacts/XfbVarying/ResourceReflection - // by calling VisitFields on the element itself; the tables never recurse. - template requires std::same_as, TypeFacts> - void VisitFields(Self& a, V&& v) { v("isArray", a.isArray); … v("basicType", a.basicType); } // 20 - …ResourceReflection (14, `type` visited as a value)… XfbVarying (11)… - …LinkArtifacts: every member EXCEPT `program` (52 of 53) - `program` is null for every archived instance by - construction (ProgramTranslationCache.h:42-50) and must never be serialized… - …SpirvArtifacts (8)… - - // ---- trip wires ---- - static_assert(std::is_trivially_copyable_v && sizeof(TypeFacts) == 44); // 13 Bool + 3 pad + 7 x 4 -#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX) -#define MGL_RESOURCEREFLECTION_SIZE -#define MGL_XFBVARYING_SIZE -#define MGL_LINKARTIFACTS_SIZE -#define MGL_SPIRVARTIFACTS_SIZE -#elif defined(_LIBCPP_VERSION) && (SIZE_MAX == UINT64_MAX) && defined(MGL_ARTIFACT_SIZES_LIBCXX_PINNED) - // integrator pins these from the NDK build (see C.4); until then this branch is inert -#endif -#ifdef MGL_LINKARTIFACTS_SIZE - static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE, - "LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) (and its serializer when one exists), then update this number"); - … same for the other three … -#endif -} -``` -`VisitFields` is a free constrained template rather than a member so the moved struct bodies stay verbatim, and a single table serves `const` and non-`const` (`Self` deduces either). - -**`ProgramObject.h` alias block** (replaces `:41-104`, stays in the public section; a fully qualified RHS is mandatory — an unqualified `TypeFacts` would self-reference): -``` - using TypeFacts = MobileGL::MG_State::GLState::TypeFacts; - using ResourceReflection = MobileGL::MG_State::GLState::ResourceReflection; - using UniformReflection = ResourceReflection; - using BlockReflection = ResourceReflection; - using PipeInputReflection = ResourceReflection; - using PipeOutputReflection = ResourceReflection; - using XfbVarying = MobileGL::MG_State::GLState::XfbVarying; - using LinkArtifacts = MobileGL::MG_State::GLState::LinkArtifacts; - using SpirvArtifacts = MobileGL::MG_State::GLState::SpirvArtifacts; -``` -`MAX_UNIFORM_LOCATIONS` (`:39`, glslang-bound) and the inline accessors that spell `glslang::ElmRowMajor` (`:489,:502`) / `glslang::TQualifier::layoutLocationEnd` (`:1476`) stay in `ProgramObject.h`. - -**Ordered steps** -1. Commit 1 (pure move): create the header (types + `kInvalidUniformOffset` only), apply the `ProgramObject.h` cuts + aliases + re-export; build; add the test file + CMake stanza (alias/POD tests only); run. Message: `[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`. -2. Commit 2: drop `ProgramObject.h:14`; build; fix consumers per the table; message `[Refactor] (Program): stop ProgramObject.h including SpvcSession.h - it names no spirv-cross or SPIRV-Reflect symbol`. -3. Commit 3: the `VisitFields` tables + tripwires + the field-count tests. Message `[Feat] (Program): add the reflection-archive field tables and sizeof trip wires to ProgramArtifacts.h`. -(Three commits so the symbol report attributes churn to commit 1 only, and any `SpvcSession.h` fallout is bisectable.) - -**Constraints** -- Struct bodies verbatim (D5): both glslang-typed members stay; no field added/removed/reordered/re-typed; comments move with their structs. -- Do not leave a second definition behind (the classic failure: `GLState::TypeFacts` and `GLState::ProgramObject::TypeFacts` would both compile and silently split the type). The ODR grep below must show exactly one `struct` per name. -- I5 (`ProgramObject.h:1622-1631`): `m_artifacts`/`m_spirv` stay private; `Artifacts()`/`Spirv()` remain the only readers. Nothing in this package touches `ProgramObject.cpp`, `ProgramLinkTask.*`, `ProgramSpirvTask.*`, `MG_Backend/**` (except commit 2's include lines). -- Existing test names unchanged; `MG_Test/Program/*` test binaries untouched except the new one. - -**Tests** (`ProgramArtifactsTest.cpp`; first include is ``, then ``, then — for the alias checks only — ``): -- `AliasesAreTheSameTypes`: `static_assert(std::is_same_v)` ×5, and `ProgramObject::kInvalidUniformOffset == kInvalidUniformOffset`. -- `TypeFactsIsPodOf44Bytes`. -- `VisitFieldsCoversEveryMember` (commit 3): counting visitor → `TypeFacts` 20, `ResourceReflection` 14, `XfbVarying` 11, `LinkArtifacts` 52, `SpirvArtifacts` 8; `const` and non-`const` instances count the same. -- `SizesArePinnedOnThisToolchain`: `EXPECT_EQ(sizeof(LinkArtifacts), MGL_LINKARTIFACTS_SIZE)` under the same `#if`, plus an unconditional `RecordProperty("sizeof_LinkArtifacts", …)` (and the other three) so the number is visible in every `ctest -V` log on every platform (this is how the integrator obtains the libc++ numbers). - -**Verification (WSL, in `~/w7/p05-program-artifacts`)** -``` -cmake --build build-linux --parallel $(nproc) -ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -# test-name diff vs ~/w7/pipe exactly as in B.1 (comm -23 must be empty) -grep -rn "^\s*struct \(TypeFacts\|ResourceReflection\|XfbVarying\|LinkArtifacts\|SpirvArtifacts\)\b" MobileGL # 5 hits, all in ProgramArtifacts.h -grep -n "#include" MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h # and only -grep -c "glslang::" MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h # 2 (D5) -printf '#include \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 -clang++ -std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -fsyntax-only /tmp/probe_pa.cpp -git diff --stat 6672778b -- MobileGL/MG_Backend MobileGL/MG_Impl # empty after commit 1 (commit 2 may add include lines) -# symbol report expectation: 0 added / 0 removed / 0 resized after --strip-scope; all churn is ProgramObject::X -> X renames -python3 scripts/symbol_report.py --before ~/w7/pipe/build-linux/libMobileGL.so --after build-linux/libMobileGL.so \ - --strip-scope 'MobileGL::MG_State::GLState::ProgramObject::' --only-names 'TypeFacts,ResourceReflection,XfbVarying,LinkArtifacts,SpirvArtifacts' -``` - ---- - -### B.3 Package `p05-include-gate` (tree `~/w7/p05-include-gate`, branch `p05/include-gate`) - -Compiles and passes against the base tree (probes A/B `SKIP`, C `OK`, self-test `OK`) and needs nothing from the other two packages. - -**Files** - -| action | file | what | -|---|---|---| -| CREATE | `scripts/check_include_closure.py` | the closure gate (spec below) | -| CREATE | `scripts/symbol_report.py` | the nm/`.text` attribution (spec below) | -| CREATE | `MobileGL/MG_Test/Purity/CMakeLists.txt` | ctest wiring (below) | -| MODIFY | `MobileGL/MG_Test/CMakeLists.txt` | after `:90 add_subdirectory(Pipe)`: a 2-line comment in the `:88-89` voice + `add_subdirectory(Purity)` | -| MODIFY | `.github/workflows/test.yml` | new job `include-graph-check` inserted after `flatc-check` (after `:323`, before `:325 benchmark`); name reserved by `ARCHITECTURE.md:568` | - -**`scripts/check_include_closure.py` — specification** -- `REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))` (`gen_pipe.py:37` idiom); `argparse` with `description=__doc__`; every stdout line prefixed `include-closure: `; failures end with a GitHub `::error::` line and `sys.exit(1)` (`test.yml:829-846` house style). -- `PROBES` module-level table exactly as in B.0 (Name, Header, Tu, Forbidden, Allow=[] — policy: an allow entry needs a `Reason` string printed in the summary and a PR justification, `test.yml:831-835` — TextLimits, Why). -- Flags: `--mode {text,clang,both}` (default `text`), `--compiler PATH` (default order: `$CXX`, `clang++-20`, `clang++`, `c++`, `g++`; print which one was used), `--compile-commands PATH` (optional: take any entry whose `file` is under `MobileGL/`, keep `-D/-I/-isystem/-std`, drop `-o/-c/-MD/-MF/-MT` and the input), `--probe NAME` (repeatable), `--self-test`, `--require-all`, `--json PATH`. -- Default clang flags when no compile-commands (measured to reproduce the full-flag closure exactly, 666 lines): `-std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -H -E -o /dev/null`, cwd = `REPO_ROOT`. Before running clang mode, check `include/ska/flat_hash_map.hpp`, `3rdparty/xxHash/xxhash.h`, `3rdparty/Vulkan-Headers/include/vulkan/vulkan.h` exist; if not, fail with `run from a full checkout: git submodule update --init include/ska 3rdparty/xxHash 3rdparty/Vulkan-Headers` (never silently downgrade). -- `-H` parsing: stderr only; accept only lines matching `^(\.+) (.*)$` (g++ appends a "Multiple include guards may be useful for:" trailer that must be discarded); depth = dot count, so the chain to a violation is reconstructed by walking back to depth−1; `path = os.path.normpath(os.path.join(cwd, path))` before prefix matching (today's output contains `TextureState/../SamplerState/SamplerObject.h`); paths outside the repo are ignored. -- Text mode: transitive walk of literal `#include` lines (there are no macro-driven includes under `MobileGL/`: 3377 includes, all literal). Resolution: `"…"` → including file's directory first; then `include/`, `MobileGL/`, `MobileGL/MG_Pipe/`, `3rdparty/xxHash/`, `3rdparty/Vulkan-Headers/include/`; unresolved = leaf. Blind to `#if`; the compiler mode is the arbiter. In `--mode both` a disagreement between the modes' violation sets is itself a failure that prints both sets. -- Second assertion per probe in clang mode: `-fsyntax-only` on the probe TU (self-contained header), reported on its own line. -- Missing header (D9): `include-closure: SKIP header not present yet: `; counted in the summary; with `--require-all` a SKIP is a failure. -- Summary lines (always): `include-closure: OK|SKIP|FORBIDDEN headers in closure, forbidden (mode=…, cxx=…)`, then `include-closure: probes, skipped,

problem(s)`. On a violation print the chain, one hop per line, indented. -- Assert that the manifest contains both `value-header` and `artifacts-header` — deleting a probe is red. -- `--self-test` (always run by the ctest and the CI job; must not touch any tracked file — synthesize TUs in `tempfile.mkdtemp()`): - 1. Negative control independent of P0.5: TU `#include \n#include \n` checked with the `value-header` forbidden list → must report ≥1 violation with `RenderState.h` at depth 1. - 2. When `MGPipeValueTypes.h` exists: TU `#include \n#include \n` → must trip (the roadmap's literal "人为加回一个 MG_State include 能变红"). - 3. When `ProgramArtifacts.h` exists: TU with `#include ` added → must trip on the `artifacts-header` list; and a canned copy of the header with a third `glslang::` token must trip the text limit. - 4. Parser unit checks: a canned g++ `-H` transcript with trailer lines parses to the dot lines only; a `../`-spelled path normalises and matches. - 5. Each of 1-3 runs in every enabled mode. Zero violations anywhere → `::error:: negative control did not trip: the closure gate is not checking anything`, exit 1. - -**`MobileGL/MG_Test/Purity/CMakeLists.txt`** -``` -cmake_minimum_required(VERSION 3.14) -# The P0.5 include-closure assertions (ROADMAP P0.5; ARCHITECTURE.md:501 gate A): no MobileGL -# compilation, no GL context. Text mode only here, because this CTestTestfile also runs on the -# `test` job's runner (test.yml:149-203), which has no submodules; the compiler-backed run is the -# include-graph-check job and the developer's local `--mode both`. -find_package(Python3 COMPONENTS Interpreter) -if (Python3_Interpreter_FOUND) - add_test(NAME MobileGLPurity.IncludeClosure - COMMAND ${Python3_EXECUTABLE} ${MGL_ROOT}/scripts/check_include_closure.py --mode text --self-test) - # ctest -L unit (test.yml:198-203). NO ENVIRONMENT property: it replaces the job env (ARCHITECTURE.md:567). - set_tests_properties(MobileGLPurity.IncludeClosure PROPERTIES LABELS unit) -else() - message(STATUS "MobileGLPurity.IncludeClosure not registered: no python3 interpreter (Windows is not a correctness gate)") -endif() -``` -The integrator later appends `--require-all` (C.3). The `test` job's path rewrite (`test.yml:185-196`) only touches the cmake binary path; the script's absolute path is the same on both runners. - -**CI job** (insert after `test.yml:323`): -``` - # P0.5 interface-purity gate A (ARCHITECTURE.md:501): the two extracted headers' include closure, - # asserted on `-H` output because `nm --undefined-only` is blind to "included but not called". - # Needs a preprocessor and three header submodules, no CMake configure, no glslang sources - - # independent of build-linux for the same reason pipe-gates is. - include-graph-check: - name: Include-closure purity gate - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v6 - - name: Check out the three header submodules the closure needs - # ska/flat_hash_map.hpp, xxhash.h and vulkan/vulkan.h are the only submodule headers - # Includes.h reaches; glslang and spirv_cross are vendored under include/. - run: git submodule update --init include/ska 3rdparty/xxHash 3rdparty/Vulkan-Headers - - name: Install clang - run: sudo apt-get update && sudo apt-get install -y clang-20 - - name: Include-closure assertions and negative control - run: python3 scripts/check_include_closure.py --mode both --compiler clang++-20 --self-test -``` -(`--require-all` appended by the integrator, C.3.) - -**`scripts/symbol_report.py` — specification** (informational; `ARCHITECTURE.md:507`; name `monolith-symbol-report` from `:568`; no nm/size tooling exists in the tree today) -- `--before <.so>` `--after <.so>` `[--nm nm] [--size size] [--cxxfilt c++filt] [--markdown out.md] [--json out.json] [--strip-scope PREFIX]* [--rename-map OLD=NEW]* [--only-names A,B,…] [--threshold 0] [--self-test]`. -- Per side: `nm --defined-only -S ` → `{mangled: (type, size)}`; demangle via `c++filt` for the normalised key; apply `--strip-scope`/`--rename-map` textual substitutions to the demangled name (this folds `ProgramObject::LinkArtifacts` → `LinkArtifacts` everywhere, including template arguments such as `std::vector`); `size --format=sysv ` for `.text`/`.data`/`.bss`/Total. -- Buckets sorted by |Δ|: removed, added, resized, renamed-only (same normalised name, byte-identical size — the pure-move signature), unchanged (count). `--only-names` restricts the listing to symbols whose demangled text contains one of the names. -- Prints `symbol-report: .text B -> A (+d, +p%)` and `symbol-report: N -> M defined symbols: a added, r removed, s resized, n renamed`, then a Markdown table; always exit 0; `--fail-on-added-bytes N` reserved for a future hard gate. -- Docstring guard rails: same `CMAKE_BUILD_TYPE` on both sides (visibility presets differ, `CMakeLists.txt:578-598`), `MOBILEGL_ENABLE_LTO` OFF on both (`:108,137-146`), same compiler; print both `.so` paths and sizes in the header. -- `--self-test`: two tiny canned `nm -S` transcripts → expected buckets. - -**Verification (WSL, in `~/w7/p05-include-gate`, base tree — no new headers yet)** -``` -python3 scripts/check_include_closure.py --mode both --self-test # value-header SKIP, artifacts-header SKIP, wire-header OK, self-test OK, exit 0 -python3 scripts/check_include_closure.py --mode both --self-test --require-all; echo exit=$? # exit 1, message names the two missing headers -python3 scripts/check_include_closure.py --mode text --self-test # exit 0 (this is what the ctest runs) -python3 scripts/check_include_closure.py --mode both --compiler g++ --self-test # trailer-line handling, exit 0 -# red-for-its-reason, by hand: temporarily add `#include ` to -# MobileGL/MG_Remote/Transport/ITransport.h -> probe wire-header FORBIDDEN with the printed chain; revert. -cmake -S . -B build-linux && ctest --test-dir build-linux -R MobileGLPurity --output-on-failure -ctest --test-dir build-linux -L unit -N | grep -c MobileGLPurity # 1 (the label reaches ctest -L unit) -python3 scripts/symbol_report.py --self-test -python3 scripts/symbol_report.py --before ~/w7/pipe/build-linux/libMobileGL.so --after build-linux/libMobileGL.so # 0/0/0/0 on an unchanged tree -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/test.yml'))" # syntax only -``` -Commits: `[CI] (Purity): add scripts/check_include_closure.py - the -H include-closure gate for the P0.5 headers with an always-on negative control, wired as a unit ctest and the include-graph-check job` and `[Tooling] (Purity): add scripts/symbol_report.py - per-symbol nm/.text attribution between two libMobileGL.so builds`. - ---- - -### B.4 File-ownership table (the guarantee that the three trees never touch the same file) - -| file / glob | value-types | program-artifacts | include-gate | integrator | -|---|---|---|---|---| -| `MobileGL/MG_Pipe/MGPipeValueTypes.h` (new) | owner | – | – | – | -| `MobileGL/MG_Pipe/MGPipeTypes.h`, `scripts/gen_pipe.py`, `MobileGL/MG_Pipe/generated/*.inc` | owner | – | – | – | -| `MobileGL/MG_State/GLState/{RenderState/RenderState.h, RenderState/RenderState.cpp, SamplerState/SamplerObject.h, VertexArrayState/VertexArrayObject.h, FramebufferState/FramebufferObject.h}` | owner | – | – | – | -| `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp` | owner | – | – | – | -| `MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` (new), `ProgramObject.h` | – | owner | – | – | -| `MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp` (new), `MobileGL/MG_Test/Program/CMakeLists.txt` | – | owner | – | – | -| `scripts/check_include_closure.py`, `scripts/symbol_report.py` (new), `MobileGL/MG_Test/Purity/**` (new) | – | – | owner | – | -| `MobileGL/MG_Test/CMakeLists.txt`, `.github/workflows/test.yml` | – | – | owner | flips `--require-all` after all land | -| `MobileGL/MG_Backend/**`, `MobileGL/MG_Impl/**`, `MobileGL/MG_Util/**`, other `MG_State/**` | include-line additions only, only if the build demands, placed after the TU's existing `MG_State/GLState/...` include | include-line additions only (commit 2), placed after the TU's `ProgramObject.h` include line | – | – | -| `docs/Disaggregated/*.md` | – | – | – | owner | -| `tools/**`, `android-plugin/**`, root `CMakeLists.txt` | nobody | nobody | nobody | nobody | - -The one shared-file possibility is the "include-line additions" row: the two packages add at different anchors of a TU, so the hunks do not overlap; the integrator resolves an adjacent-hunk conflict by keeping both lines. - ---- - -## C. Integration order and what the integrator runs - -C.0 Preconditions: all three branches green on their own verification lists; `~/w7/pipe` clean at `6672778b` (fixture binaries hidden as in `wsl_integrate.sh`); capture the base library: `cp ~/w7/pipe/build-linux/libMobileGL.so ~/w7/p05-before-libMobileGL.so` (Release, `/usr/sbin/clang++`, built at `6672778b` — verify with `git -C ~/w7/pipe log -1` and `grep CMAKE_BUILD_TYPE ~/w7/pipe/build-linux/CMakeCache.txt`); capture `ctest -N` names and `grep -rc "pGLContext" MobileGL/MG_Backend | sort > ~/w7/p05-before-pglcontext.txt`. - -C.1 Order: **include-gate → value-types → program-artifacts.** The gate first, so probes A and B flip from SKIP to OK as each header lands and the integrator runs the real gate on each merge. The p05 trees are worktrees of `~/w7/pipe` (`git worktree list` shows them; branches are local), so the P0 helper's fetch step is unnecessary; per slug: -``` -cd ~/w7/pipe && git checkout -q feat/disaggregated -git checkout -q p05/ && git rebase feat/disaggregated # conflict => abort, report, stop -git checkout -q feat/disaggregated && git merge --ff-only p05/ -cmake --build build-linux --parallel $(nproc) && ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -python3 scripts/check_include_closure.py --mode both --self-test # after value-types: A OK; after artifacts: A+B OK -``` -(`scratchpad/wf2/wsl_integrate.sh` is the P0 shape of this loop; it hard-codes `p0/` and `~/w7/p0-` and fetches — copy it to `wsl_integrate_p05.sh` with the prefix changed and the fetch removed rather than editing it.) - -C.2 After all three: `python3 scripts/check_include_closure.py --mode both --self-test --require-all` (exit 0); `ctest -N` name diff against the C.0 capture (`comm -23` empty; additions listed in the merge message); `git diff --stat 6672778b -- MobileGL/MG_Backend` shows include-line additions only (or nothing); `grep -rc "pGLContext" MobileGL/MG_Backend | sort | diff - ~/w7/p05-before-pglcontext.txt` empty; `python3 scripts/gen_pipe.py --check`. - -C.3 Flip the ratchet (one commit, integrator-owned): append `--require-all` to the ctest COMMAND in `MobileGL/MG_Test/Purity/CMakeLists.txt` and to the CI step in `test.yml`; message `[CI] (Purity): require both P0.5 closure probes now that the headers exist`. - -C.4 Symbol report: `python3 scripts/symbol_report.py --before ~/w7/p05-before-libMobileGL.so --after build-linux/libMobileGL.so --strip-scope 'MobileGL::MG_State::GLState::ProgramObject::' --markdown ~/w7/p05-symbol-report.md`. Expected: `.text` unchanged (the library gains no code: the `VisitFields` templates are uninstantiated outside the test binary, all moved types are declarations); 0 added / 0 removed / 0 resized after normalisation; N renamed. Anything else is attributed line by line in the report before the `dev` merge. Paste the two summary lines into the merge commit. Android/libc++ sizes: build the plugin APK once (`gradle -p android-plugin :app:assemblePluginRelease`, see `apk.yml:78`) with a temporary `static_assert(sizeof(LinkArtifacts) == 1)` (and the other three) to read the values from the NDK clang notes, then pin the `_LIBCPP_VERSION` branch of `ProgramArtifacts.h` and define `MGL_ARTIFACT_SIZES_LIBCXX_PINNED` there; message `[Test] (Program): pin the libc++ artifact sizes`. - -C.5 CI and docs: `gh workflow run test.yml --ref feat/disaggregated` (and `apk.yml` if dispatchable) — record run URLs; `include-graph-check`, `pipe-gates`, `test` green. Then docs (integrator-owned): `docs/Disaggregated/README.md:3` status → P0.5 landed with the commit hash; `ROADMAP.md:16` ✅ with the "7 includers" wording corrected to "8 includers, none edited (in-class aliases)"; `ARCHITECTURE.md:260` note that `DynamicBackendParameters` stayed (D1) and its `:563` stale citation `MG_Test/CMakeLists.txt:93-95` → `:100-103`. Then `[Merge]` to `dev` per the milestone flow, and schedule the full `gl44to46` caselist (`ROADMAP.md:34`) on the two devices per the Wave-7 protocol — device time, not on the integration critical path. - ---- - -## D. Risks and mitigations - -| risk | mitigation | -|---|---| -| **ODR — a struct copied, not moved.** Two same-named definitions in `namespace MobileGL` (value types) compile per-TU and silently break Espryt's `offsetof` shadow if they drift; for the artifacts, `GLState::TypeFacts` vs `GLState::ProgramObject::TypeFacts` compile as two different types. | The per-package ODR greps (exactly one `struct X` per name) are mandatory verification, and the alias `static_assert(is_same_v<…>)` in `ProgramArtifactsTest.cpp` catches the artifacts case at compile time. | -| **Include cycles.** MG_State now includes MG_Pipe (`RenderState.h`, `SamplerObject.h`, `VertexArrayObject.h`, `FramebufferObject.h` → `MGPipeValueTypes.h`). | `MGPipeValueTypes.h` includes only ``/`VectorTypes.h` and forward-declares `BufferObject`; the gate makes any MG_State include in it red; MG_Pipe is "永远进构建" and below MG_State (`ARCHITECTURE.md:550-561`), so the direction is the intended one. `ProgramArtifacts.h` includes nothing from `ProgramState/`, so `ProgramObject.h → ProgramArtifacts.h` cannot cycle. | -| **Lost transitive includes.** `RenderState.h` stops dragging `FramebufferObject.h → TextureObject.h → RenderbufferObject.h → SamplerObject.h …` (8 headers) into the 73 `Core.h` consumers and its 5 direct includers; `ProgramObject.h` (commit 2) stops dragging `SpvcSession.h`/``. | Only the compiler enumerates the fallout; the rule is "add the direct include at the consumer, never at the forwarding header", one anchor per package (B.4), listed in the commit message. `RenderState.cpp` is the one known case (fixed in the package). | -| **MSVC / Android not built locally.** `test.yml` builds Linux only (clang-20, `build-linux`); `apk.yml` builds the NDK APK; neither runs on `feat/disaggregated` pushes; the Windows box is not a correctness gate. | Nothing in P0.5 is platform-specific: no `SizeT` widening (D1/D5), no language feature beyond C++20 constraints (`requires std::same_as`; fine on MSVC 19.3x and NDK r27 clang), `offsetof` on `RenderStateParameters` is already compiled on all three toolchains by `DirectGLES.cpp`. Container-bearing `sizeof` assertions are libstdc++-only until the integrator pins libc++ (C.4); MSVC is unasserted. The integrator's `workflow_dispatch` of `apk.yml` is the Android build gate; a `cmake --build` in the Windows worktree before the `dev` merge is cheap and recommended, not required. | -| **glslang types leaking by value in the archive.** `LinkArtifacts::program` and `uniformInitialValues` keep glslang types (D5). | Symbol-free by construction (type-erased deleter; aggregate with inline implicit members); the gate pins `glslang::` at exactly 2 occurrences in `ProgramArtifacts.h`, so a third leak is red; `VisitFields(LinkArtifacts)` excludes `program` and the future deserializer must assert it null (`ProgramTranslationCache.h:42-50` already asserts that at insert). The `UniformInitializer` re-typing is the recorded P7 follow-up, together with narrowing `ProgramTranslationCache.h`/`ProgramInterface.cpp` to the new header and moving `MAX_UNIFORM_LOCATIONS`' glslang RHS behind a client-side assert. | -| **`PipeInputs` (P1) needs.** `ARCHITECTURE.md:328`: accessors return "exactly the types the backend reads today"; `:344`: 293 `pGLContext->` sites + 58 non-arrow lines are `sed`'d mechanically; `ResidualValueBlock` needs `offsetof` computability; `MOBILEGL_PIPE_VERIFY` keeps its `MG_State` includes (`:369`). | D3 keeps every type's qualified name; the value header keeps standard layout and asserts it; `MG_Backend/**` is diff-empty apart from possible include lines; the `pGLContext` count is captured before/after (C.0/C.2); the value header coexists with `MG_State` includes (no mutual exclusion). `PipeFields.def` field lists for the value types are P1 work (D8), and `MEMCMP_FALLBACK_TYPES` in `gen_pipe.py:166-171` stays as is until then. | -| **`RenderStateParameters` is about to change in P2** (`FramebufferSrgb`/`DepthClamp` storage, `ROADMAP.md:18,78`), moving `sizeof` off 1168. | After P0.5 every 1168-dependent number lives in exactly two places: the value header's assertions and `MGL_RESIDUAL_BLOCK_SIZE` (`MGPipeTypes.h:535`); the test twins name both. P2 updates both in one commit. | -| **A gate that is green because it never ran.** `feat/disaggregated` is not a trigger branch (`test.yml:3-8`); a mislabelled ctest never runs under `ctest -L unit`. | `LABELS unit` verified by `ctest -L unit -N`; the ctest runs in every local build; the integrator dispatches the workflow and records the URL (D10); `--self-test` is always on, so a broken parser cannot pass silently; `--require-all` after integration makes SKIP red. | -| **`#pragma once` with two include spellings** (`` and ``, `CMakeLists.txt:531,535`) on case-insensitive/symlinked Windows worktrees. | The macro guard in `MGPipeValueTypes.h`. | -| **`nm` attribution looks catastrophic** because de-nesting renames every mangled name mentioning the five artifact types (including every `std::vector<…>` instantiation). | `symbol_report.py --strip-scope` folds them into the renamed-only bucket with byte-identical sizes; the value-types package contributes zero churn by D3, so the two packages are separable in the report by construction. | diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-review-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-review-v1.md deleted file mode 100644 index 8e82e73f..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-review-v1.md +++ /dev/null @@ -1,95 +0,0 @@ -# Adversarial review — package `p05-include-gate` (v1) - -Tree `~/w7/p05-include-gate`, branch `p05/include-gate` = `fe3dc1dd` + `2318f6ae` on `6672778b`. -Reviewer stance: refute "correct, complete, pure, meets the brief (A, B.0, B.3, C, D)". -Verdict: **approved — 0 majors, 9 minors.** Tree left exactly as found (`git status --porcelain --untracked-files=all | grep -v build-linux/` empty before and after every experiment). - -All scripts used are under `scratchpad/wf3/p05-include-gate-review/s01..s07.sh`; long outputs under `~/w7/review-p05g/`. - -## 1. Scope and purity of the diff - -`git diff --stat 6672778b..HEAD` = exactly the five B.4-assigned files, 1089 insertions, 0 deletions: -`.github/workflows/test.yml` (+26), `MobileGL/MG_Test/CMakeLists.txt` (+3), `MobileGL/MG_Test/Purity/CMakeLists.txt` (+15), `scripts/check_include_closure.py` (+659), `scripts/symbol_report.py` (+386). -`git diff --stat 6672778b -- MobileGL/MG_Backend MobileGL/MG_State MobileGL/MG_Pipe` empty. No fixture binaries. No CRLF (`xargs grep -lU $'\r'` empty). No attribution trailers in either commit message (`git log --format=%B | grep -i "co-authored\|signed-off"` empty); subjects are `[Type] (Scope): …` with `- ` bullets. -No C++ was added, so "copied vs moved", member order, alignment, constexpr-ness and static_assert coverage are not applicable to this package; the symbol report on the rebuilt library is 0/0/0/0 (section 3). - -## 2. Spec conformance (B.3) — line-by-line against `scripts/check_include_closure.py` - -| B.3 item | where | verdict | -|---|---|---| -| `REPO_ROOT` idiom, `description=__doc__`, `include-closure: ` prefix, `::error::` + exit 1 | `:62,:64,:161-166,:544,:651-653` | as specified | -| `PROBES` table with Name/Header/Tu/Forbidden/Allow/TextLimits/Why, values identical to B.0 | `:72-118` | byte-compared against the B.0 table: identical (six forbidden prefixes for artifacts incl. the `ProgramState/Shader` prefix, `glslang::` ≤ 2) | -| flags `--mode/--compiler/--compile-commands/--probe/--self-test/--require-all/--json` | `:546-558` | all present; default mode `text` | -| compiler order `$CXX, clang++-20, clang++, c++, g++`, printed | `:141,:286-303,:591` | as specified | -| default flags = the measured 6, cwd = REPO_ROOT | `:125-132,:576` | identical to the brief's line | -| prereq check that refuses rather than downgrades | `:135-139,:340-345` | verified: without the submodules, `--mode clang` exits 1 with the exact `git submodule update --init …` message (s06) | -| `-H` parse: stderr only, `^(\.+) (.*)$`, normpath, outside-repo ignored | `:156,:169-194,:348-352` | as specified; g++ trailer verified live and by canned transcript | -| text mode: literal walk, `"…"` includer-dir first then the 5 search dirs, unresolved = leaf | `:145-151,:197-241` | as specified | -| `--mode both` disagreement is a failure printing both sets | `:624-631` | verified (experiment B3) | -| `-fsyntax-only` second assertion, own line | `:410-424,:632-634` | verified | -| SKIP semantics + `--require-all` | `:603-615` | verified (section 4) | -| manifest assertion for both required probes | `:121,:561-566` | present | -| self-test 1–5, tempdir only, never touches a tracked file | `:431-540,:572` | verified incl. controls 2/3 and the third-`glslang::` control, which only arm when the headers exist (experiments B1, C1) | -| CMake stanza | `MG_Test/Purity/CMakeLists.txt` | verbatim to the brief; inserted after `add_subdirectory(Pipe)` at `MG_Test/CMakeLists.txt:91-93` | -| CI job after `flatc-check`, before `benchmark`, steps verbatim | `test.yml:325-349` | YAML parses (Windows PyYAML): 13 jobs, neighbours `['flatc-check','include-graph-check','benchmark']`, no `needs`, triggers untouched (D10) | - -`scripts/symbol_report.py`: every flag in the spec list exists (`:269-289`); `nm --defined-only -S` / `c++filt` / `size --format=sysv` (`:131-138`); buckets sorted by |Δ| (`:189-191`); the two summary lines + Markdown (`:324-363`); always exit 0 (`:382`); guard rails in the docstring (`:24-31`); `--self-test` with two canned transcripts (`:209-263`). - -Declared deviations (result file §3, items 1–6) were each checked and are accepted: `--strip-scope` de-nesting is the only semantics that produces the fold the brief asks for (a literal prefix delete would put the two spellings in removed/added); one SKIP line per probe; disagreement check on probes only; the extra normalised-names line; local clang 22/g++ 16 vs CI clang-20; cosmetic YAML spacing. - -## 3. Independent re-run of the verification list (all in `~/w7/p05-include-gate`, s04) - -``` -cmake --build build-linux -j 16 -> [47/47] … (incremental, clean) -ctest --test-dir build-linux -L unit --no-tests=error -j 8 - -> 1459 tests, 0 failed (2 skipped LogLevel.* as on base) -ctest -N name diff vs ~/w7/pipe/build-linux -> lost: (none); added: MobileGLPurity.IncludeClosure ; 1327 -> 1328 -ctest --test-dir build-linux -L unit -N | grep -c MobileGLPurity -> 1 -python3 scripts/gen_pipe.py --check -> generated files are up to date, exit 0 -python3 scripts/check_include_closure.py --mode both --self-test -> exit 0, "3 probes, 2 skipped, 0 problem(s)", 2 trips -python3 scripts/check_include_closure.py --mode text --self-test -> exit 0, 1 trip -python3 scripts/check_include_closure.py --mode both --self-test --require-all -> exit 1, two "SKIP is a failure under --require-all" lines -python3 scripts/check_include_closure.py --mode both --compiler g++ --self-test -> exit 0, 2 trips -python3 scripts/symbol_report.py --self-test -> OK (2 canned transcripts, 5 buckets) -python3 scripts/symbol_report.py --before ~/w7/pipe/build-linux/libMobileGL.so --after build-linux/libMobileGL.so - -> .text 10792579 -> 10792579 (+0); 27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed; 27060 unchanged -ODR grep (8 value-type structs) -> 8 hits, all still in MG_State (nothing moved by this package, as expected) -git status -> clean -``` - -Generated CTestTestfile (`build-linux/MobileGL/MG_Test/Purity/CTestTestfile.cmake`): `add_test(MobileGLPurity.IncludeClosure "" ".../MobileGL/MG_Test/../../scripts/check_include_closure.py" "--mode" "text" "--self-test")`, `LABELS "unit"`, no ENVIRONMENT. `build-linux/MobileGL/MG_Test/CTestTestfile.cmake:20 subdirs("Purity")`, and the runtime tarball packs `${BUILD_DIR}/MobileGL/MG_Test` whole (`test.yml:126`), so the `test` job's `ctest -L unit` reaches it the same way it reaches `Pipe`. - -## 4. Gate semantics and red-for-its-reason (s05, every edit restored by an EXIT trap) - -**A. Manual negative control on the live probe.** `sed` inserted `#include ` after `ITransport.h:35`; `--mode both --probe wire-header` → exit 1, both modes `wire-header FORBIDDEN … 1 forbidden` with the identical 3-hop chain `ITransport.h → RenderState.h → Includes.h` (76 / 672 headers in closure vs 2 / 42 clean). `git checkout --` restored; 0 dirty lines. - -**B. Probe A exercised with a scratch `MobileGL/MG_Pipe/MGPipeValueTypes.h`** (never run before; deleted afterwards): -- pure (``, ``, ``, ``): `value-header OK 66 / 654 headers, 0 forbidden`, SELF-CONTAINED OK, **control-2 arms and trips in both modes** (9 violations, RenderState.h at depth 1), `3 probes, 1 skipped, 0 problem(s)`, exit 0. So the B.0 forbidden set is satisfiable with `Includes.h` in the closure, and the brief's layout will go green. -- `RenderState.h` added back: both modes `FORBIDDEN … 9 forbidden`, chain printed, exit 1 — the roadmap's literal red. -- `` (the D1 route): `FORBIDDEN … 2 forbidden` both modes — the gate would catch `DynamicBackendParameters` being moved after all. - -**C. Probe B exercised with a scratch `ProgramArtifacts.h`** (``, ``, exactly two `glslang::`): `artifacts-header OK 65 / 653, 0 forbidden`, SELF-CONTAINED OK, control-3 trips in both modes (16 violations, ShaderObject.h at depth 1), text-limit control trips (`glslang:: occurs 3 times, budget is 2`), exit 0. Then `#include "ShaderObject.h"` (quoted spelling) → `FORBIDDEN … 16 forbidden` with chain; a third `glslang::` in a comment → `FORBIDDEN`, `text limit: glslang:: occurs 3 times`. `MobileGL/Config.h` is not reached via `Includes.h` (0 forbidden), so the Config.h rule is satisfiable too. - -**D. CI file-set simulation** (s06): tracked files of `MobileGL include scripts` only + the three submodules linked in (exactly what `include-graph-check` sees) → `--mode both --self-test` exit 0 with the same 42-header wire closure and 2 trips. Every header the closure reaches outside the submodules (`include/EGL/egl.h`, `include/GL/*`, `include/GLES3/gl32.h`, `include/glslang/*`, `include/spirv_cross/spirv_cross_c.h`) is `git ls-files`-tracked, so the job does not depend on `update_glslang_sources.py` or system GL headers. Same set without the submodules (the `test` job's checkout) → `--mode text --self-test` exit 0, and `--mode clang` refuses with the submodule message. `apt-get install -y clang-20` is the same mechanism `build-linux` already uses (`test.yml:61`), so the install step is no less reliable than the existing lane. - -## 5. Majors - -None. - -## 6. Minors (none blocks; ordered by usefulness to the integrator) - -1. **`flags_from_compile_commands` corrupts a two-token `-isystem DIR`** (`check_include_closure.py:327-330`): the path token is dropped and the bare `-isystem` then swallows the next flag. Repro: canned entry `-DFOO=1 -isystem /opt/sys/include -I…/MobileGL -std=gnu++23` → `['-DFOO=1', '-isystem', '-I/…/MobileGL', '-std=gnu++23']`. Latent here: this tree's `compile_commands.json` uses no `-isystem` (Init.cpp entry checked), and neither CI nor the ctest passes `--compile-commands`. Fix: treat `-isystem` like `-o` (keep both tokens). -2. **`--require-all` only inspects selected probes**: `--mode text --probe wire-header --require-all --self-test` → exit 0 (`1 probes, 0 skipped, 0 problem(s)`) with both P0.5 headers absent (`main:593-615`). Not reachable from the CI/ctest command lines, which never pass `--probe`, but the ratchet has a hole for a hand-run. `--probe value-header --require-all` correctly exits 1. -3. **Text-only ctest lane has no arbiter for `#if`-guarded includes**: an `#if 0`-guarded `RenderState.h` include in the value header gives `FORBIDDEN 9 forbidden` in text mode and `OK` in clang; `--mode both` reports MODE DISAGREEMENT (2 problems). This is the brief's design, but it means the unit ctest can go red for something the compiler never includes. Rule for the two header authors: no conditionally compiled includes of forbidden paths, ever. -4. **`count_text_limits` counts comments** (`:272-283`): the brief's own suggested comment line for `ProgramArtifacts.h` ("`glslang::` appears exactly twice below (D5)") is itself a third token and turns probe B red (experiment C2). The program-artifacts author must spell it without the `::`, or the integrator sees a spurious FORBIDDEN on the first real run. -5. **`tempfile.mkdtemp` is never removed** (`:572`); two `/tmp/mgl-include-closure-*` directories were left behind by the review runs. Harmless on CI runners; a `shutil.rmtree` in a `finally` would do. -6. **ERROR + disagreement interplay**: when clang-mode preprocessing fails for a probe, `run_probe` returns `set()` (`:387`) and the disagreement check (`:624-631`) can print a spurious MODE DISAGREEMENT on top of the ERROR. Cosmetic; the run is red either way. -7. **`symbol_report.py` folding hides a count change**: with `--strip-scope`, the copied-not-moved case (both `ProgramObject::LinkArtifacts::Reset()` and `LinkArtifacts::Reset()` present after) surfaces as `resized +16`, not `added`, and the per-name `count` (1→2) is not in any table (`:150-157,:161-192`). Visible, but mislabelled; the ODR grep stays the primary defence, as D says. -8. **`flags_from_compile_commands` takes the first entry whose absolute path contains `MobileGL/`** (`:311-313`): on the Windows main worktree (`…/FoldCraftLauncher/MobileGL/`) every entry matches, including 3rdparty TUs. WSL trees are unaffected; only matters for a developer using the flag on Windows. -9. **Manifest assertion is two literals in one file** (`:121` vs `:72-118`): it guards against accidental deletion of a probe, not against a deliberate edit that removes both. Acceptable for its stated purpose ("deleting a probe is red"). - -## 7. Notes for the integrator (not findings) - -- Probes A and B have now been exercised end-to-end with scratch headers built from the brief's own include lists (section 4 B/C); both go OK, their controls arm, and each forbidden rule was tripped at least once. Expect `1 skipped` after value-types lands and `0 skipped` after program-artifacts. -- The `--require-all` flip (C.3) is one appended token in each of `MG_Test/Purity/CMakeLists.txt:10` and `test.yml:349`. -- `clang++-20` is absent in WSL; local runs use clang 22 / g++ 16 and produce the same 42-header wire closure as the brief's measured defaults. diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-v1.md deleted file mode 100644 index 84d72c94..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/include-gate-v1.md +++ /dev/null @@ -1,280 +0,0 @@ -# P0.5 package `p05-include-gate` — result (v1) - -Tree `~/w7/p05-include-gate`, branch `p05/include-gate`, based on `feat/disaggregated @ 6672778b`. -Nothing pushed. Neither P0.5 header exists in this tree, so probes A and B are SKIP by design (D9). - -## 1. Commits - -| sha | subject | -|---|---| -| `fe3dc1dd` | `[CI] (Purity): add scripts/check_include_closure.py - the -H include-closure gate for the P0.5 headers with an always-on negative control, wired as a unit ctest and the include-graph-check job` | -| `2318f6ae` | `[Tooling] (Purity): add scripts/symbol_report.py - per-symbol nm/.text attribution between two libMobileGL.so builds` | - -`git diff --stat 6672778b HEAD`: - -``` - .github/workflows/test.yml | 26 ++ - MobileGL/MG_Test/CMakeLists.txt | 3 + - MobileGL/MG_Test/Purity/CMakeLists.txt | 15 + - scripts/check_include_closure.py | 659 +++++++++++++++++++++++++++++++++ - scripts/symbol_report.py | 386 +++++++++++++++++++ - 5 files changed, 1089 insertions(+) -``` - -Exactly the five files the B.4 ownership table assigns to this package. Working tree clean apart -from the untracked `build-linux/`. All new/modified files are LF (`grep -lU $'\r'` empty). - -Absolute paths: -- `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/scripts/check_include_closure.py` -- `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/scripts/symbol_report.py` -- `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/MobileGL/MG_Test/Purity/CMakeLists.txt` -- `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/MobileGL/MG_Test/CMakeLists.txt` -- `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/.github/workflows/test.yml` - -## 2. Verification — every command and its actual output - -Environment: WSL Arch, `python3` 3.14.4, `/usr/sbin/clang++` = clang 22.1.6, `/usr/sbin/g++` = GCC -16.1.1. `clang++-20` does not exist in WSL, so the local runs used clang 22 and g++ 16; the CI job -still pins `clang++-20` as the brief specifies. - -### 2.1 `python3 scripts/check_include_closure.py --mode both --self-test` → exit 0 - -``` -include-closure: compiler: /usr/sbin/clang++ -include-closure: value-header SKIP header not present yet: MobileGL/MG_Pipe/MGPipeValueTypes.h -include-closure: artifacts-header SKIP header not present yet: MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h -include-closure: wire-header OK 2 headers in closure, 0 forbidden (mode=text, cxx=-) -include-closure: wire-header OK 42 headers in closure, 0 forbidden (mode=clang, cxx=/usr/sbin/clang++) -include-closure: wire-header SELF-CONTAINED OK (-fsyntax-only, cxx=/usr/sbin/clang++) -include-closure: self-test: negative controls (must trip) in mode(s) text, clang -include-closure: self-test parser: trailer discarded, `../` normalised and matched -include-closure: self-test control-1 (independent of P0.5) [text]: tripped, 9 violation(s), MobileGL/MG_State/GLState/RenderState/RenderState.h at depth 1 -include-closure: self-test control-1 (independent of P0.5) [clang]: tripped, 9 violation(s), MobileGL/MG_State/GLState/RenderState/RenderState.h at depth 1 -include-closure: self-test: 2 negative-control trip(s), parser checks OK -include-closure: 3 probes, 2 skipped, 0 problem(s) -``` - -This is the required shape: **A SKIP, B SKIP, C OK, self-test OK, exit 0**. The two modes agreed on -the wire-header violation set (empty), so the `--mode both` disagreement check stayed silent. - -### 2.2 `--mode text --self-test` (what the ctest runs) → exit 0 - -Same three probe lines, `self-test: 1 negative-control trip(s), parser checks OK`, -`3 probes, 2 skipped, 0 problem(s)`. No compiler and no submodules touched. - -### 2.3 `--mode both --self-test --require-all` → **exit 1**, names both missing headers - -``` -include-closure: value-header SKIP header not present yet: MobileGL/MG_Pipe/MGPipeValueTypes.h -include-closure: value-header SKIP is a failure under --require-all: MobileGL/MG_Pipe/MGPipeValueTypes.h does not exist -include-closure: artifacts-header SKIP header not present yet: MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h -include-closure: artifacts-header SKIP is a failure under --require-all: MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h does not exist -include-closure: wire-header OK 2 headers in closure, 0 forbidden (mode=text, cxx=-) -include-closure: wire-header OK 42 headers in closure, 0 forbidden (mode=clang, cxx=/usr/sbin/clang++) -include-closure: wire-header SELF-CONTAINED OK (-fsyntax-only, cxx=/usr/sbin/clang++) -... -include-closure: 3 probes, 2 skipped, 2 problem(s) -::error::include-closure gate failed: 2 problem(s); see the chains above -exit=1 -``` - -### 2.4 `--mode both --compiler g++ --self-test` → exit 0 (trailer-line handling) - -``` -include-closure: compiler: /usr/sbin/g++ -include-closure: wire-header OK 33 headers in closure, 0 forbidden (mode=clang, cxx=/usr/sbin/g++) -include-closure: wire-header SELF-CONTAINED OK (-fsyntax-only, cxx=/usr/sbin/g++) -include-closure: self-test: 2 negative-control trip(s), parser checks OK -include-closure: 3 probes, 2 skipped, 0 problem(s) -``` - -g++ appends a `Multiple include guards may be useful for:` paragraph of bare `/usr/include/...` -paths to its `-H` output (confirmed by hand: 702 stderr lines, of which the trailer is ~40). The -`^(\.+) (.*)$` filter discards it, and `--self-test`'s canned-transcript check pins that behaviour -independently of which compiler is installed. - -### 2.5 Red-for-its-reason (the manual negative control on the one live probe) - -Temporarily added `#include ` to -`MobileGL/MG_Remote/Transport/ITransport.h`, then `python3 scripts/check_include_closure.py ---mode both --probe wire-header --self-test`: - -``` -include-closure: wire-header FORBIDDEN 76 headers in closure, 1 forbidden (mode=text, cxx=-) -include-closure: forbidden: MobileGL/Includes.h (rule MobileGL/Includes.h) reached by: -include-closure: MobileGL/MG_Remote/Transport/ITransport.h -include-closure: MobileGL/MG_State/GLState/RenderState/RenderState.h -include-closure: MobileGL/Includes.h -include-closure: wire-header FORBIDDEN 672 headers in closure, 1 forbidden (mode=clang, cxx=/usr/sbin/clang++) -include-closure: forbidden: MobileGL/Includes.h (rule MobileGL/Includes.h) reached by: -include-closure: MobileGL/MG_Remote/Transport/ITransport.h -include-closure: MobileGL/MG_State/GLState/RenderState/RenderState.h -include-closure: MobileGL/Includes.h -include-closure: 1 probes, 0 skipped, 1 problem(s) -::error::include-closure gate failed: 1 problem(s); see the chains above -exit=1 -``` - -Both modes went red with an identical three-hop chain; the closure grew 2 → 76 (text) and 42 → 672 -(clang). Reverted with `git checkout --`; `git status --porcelain` for that path is empty. - -### 2.6 Other flag paths exercised - -- `--probe wire-header --json /tmp/p05g.json`: exit 0, JSON carries `modes`, per-mode `results` - (`headers` 2 / 42, `forbidden` 0), `skipped`, `problems`, `self_test`, `require_all`. -- `--probe nope`: `::error::unknown probe(s): nope`, exit 1. -- `--compile-commands build-linux/compile_commands.json --mode clang --probe wire-header`: - `flags from build-linux/compile_commands.json (27 tokens), cwd=/home/swung/w7/p05-include-gate/build-linux`, - then `wire-header OK 42 headers in closure, 0 forbidden` — the same closure as the default flags, - which is the point of having measured the defaults. -- Manifest assertion: `REQUIRED_PROBE_NAMES = ("value-header", "artifacts-header")` is checked - before anything else runs, so deleting a probe is red. - -### 2.7 ctest wiring - -``` -$ cmake -S . -B build-linux # exit 0 -$ ctest --test-dir build-linux -R MobileGLPurity --output-on-failure - Start 1064: MobileGLPurity.IncludeClosure -1/1 Test #1064: MobileGLPurity.IncludeClosure .... Passed 0.08 sec -100% tests passed, 0 tests failed out of 1 -Label Time Summary: -unit = 0.08 sec*proc (1 test) - -$ ctest --test-dir build-linux -L unit -N | grep -c MobileGLPurity -1 -``` - -Full unit suite (`ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -j 8`): - -``` -100% tests passed, 0 tests failed out of 1459 -Label Time Summary: unit = 106.08 sec*proc (1459 tests) -Total Test time (real) = 13.42 sec -``` - -Test-name diff against the read-only reference tree `~/w7/pipe`: - -``` -comm -23 before after -> (empty; no test name lost) -comm -13 before after -> MobileGLPurity.IncludeClosure -1327 before / 1328 after -``` - -### 2.8 `.github/workflows/test.yml` - -`python -c "import yaml,sys; yaml.safe_load(open(sys.argv[1]))"` was run with the **Windows** -interpreter against a copy of `//wsl.localhost/Arch/home/swung/w7/p05-include-gate/.github/workflows/test.yml` -(WSL's python has no `yaml`): - -``` -parsed OK; jobs: 13 -order around flatc-check: ['integration', 'flatc-check', 'include-graph-check', 'benchmark'] -include-graph-check present: True -name: Include-closure purity gate -runs-on: ubuntu-latest -steps: -- name: Checkout repo - uses: actions/checkout@v6 -- name: Check out the three header submodules the closure needs - run: git submodule update --init include/ska 3rdparty/xxHash 3rdparty/Vulkan-Headers -- name: Install clang - run: sudo apt-get update && sudo apt-get install -y clang-20 -- name: Include-closure assertions and negative control - run: python3 scripts/check_include_closure.py --mode both --compiler clang++-20 --self-test -``` - -Inserted immediately after `flatc-check` and before `benchmark`, as B.3 requires. Triggers -untouched (D10). - -### 2.9 `scripts/symbol_report.py` - -`python3 scripts/symbol_report.py --self-test` → `symbol-report: self-test: OK (2 canned -transcripts, 5 buckets)`, exit 0. The canned pair pins removed / added / resized / renamed-only / -unchanged, including that a de-nested member folds to *renamed*, not to added+removed. - -Two real builds — `~/w7/pipe/build-linux/libMobileGL.so` (read-only reference) vs this tree's -`build-linux/libMobileGL.so`, both Release / `/usr/sbin/clang++` / LTO off: - -``` -symbol-report: before: /home/swung/w7/pipe/build-linux/libMobileGL.so (19101056 bytes on disk) -symbol-report: after : build-linux/libMobileGL.so (19101056 bytes on disk) -symbol-report: .text 10792579 -> 10792579 (+0, +0.000%) -symbol-report: .data 76824 -> 76824 (+0) -symbol-report: .bss 1296872 -> 1296872 (+0) -symbol-report: .rodata 1534938 -> 1534938 (+0) -symbol-report: Total 17209819 -> 17209819 (+0) -symbol-report: 27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed -symbol-report: 27060 -> 27060 normalised names, 27060 unchanged (name, size and mangling all identical) -``` - -**0 added / 0 removed / 0 resized / 0 renamed** on an unchanged tree, as expected (this package -adds no C++). `--markdown` output was also written and checked. - -Synthetic `--strip-scope` rename case (two purpose-built .so files, `N::Outer::Inner::f` de-nested -to `N::Inner::f`, plus an untouched `N::Stable`): - -``` -without --strip-scope : 17 -> 17 defined symbols: 1 added, 1 removed, 0 resized, 0 renamed -with --strip-scope 'N::Outer::' : - 17 -> 17 defined symbols: 0 added, 0 removed, 0 resized, 1 renamed, 16 unchanged - -### Renamed only (same size) (1) -| normalised symbol | before mangling | after mangling | -| N::Inner::f(int) | _ZN1N5Outer5Inner1fEi | _ZN1N5Inner1fEi | -``` - -That is exactly the shape the `p05-program-artifacts` package's report is supposed to have. - -## 3. Deviations from the brief, with reasons - -1. **`--strip-scope` semantics.** The brief describes it as a textual substitution that "folds - `ProgramObject::LinkArtifacts` → `LinkArtifacts`". A literal *delete the whole prefix* would - break the fold: the before side becomes `LinkArtifacts::Reset()` while the after side is - `MobileGL::MG_State::GLState::LinkArtifacts::Reset()`, so the two would land in the - removed/added buckets — the opposite of the intent. Implemented as a **de-nesting**: - `--strip-scope 'A::B::C::'` rewrites every `A::B::C::X` to `A::B::X` (it deletes only the final - class component), which produces the stated fold and works inside template arguments too. The - behaviour is documented in `--help` and pinned by `--self-test`; C.4's command line is unchanged. -2. **One SKIP line per probe, not one per mode.** The brief's summary-line template carries a - `mode=` field, but whether a header exists is mode-independent; printing it twice under - `--mode both` was noise and double-counted the skip. A SKIP is now emitted once, before either - mode runs, and counted once. `--require-all` adds its own explicit line naming the header. -3. **The mode-disagreement check is applied to probes only, not to the self-test controls.** For a - probe, a text/clang divergence is a real signal and fails the run (implemented, prints both - sets). For the negative controls, whose TUs deliberately drag in the whole `MG_State` tree, text - mode's blindness to `#if` can legitimately produce a different violation *set* from clang's; the - self-test therefore requires each control to trip **in every enabled mode** with the expected - depth-1 path, which is the property that matters, rather than comparing sets. -4. **Extra output line in `symbol_report.py`.** `27799 -> 27799 defined symbols` and - `27060 unchanged` are different denominators (several mangled symbols fold to one normalised - demangled name). Added an explicit `N -> M normalised names` line so the two numbers cannot be - misread as 739 lost symbols. -5. **Local compiler.** `clang++-20` does not exist in WSL, so the local `--mode both` runs used - clang 22 (default resolution order) and g++ 16 (explicit `--compiler g++`). The CI job pins - `clang++-20` exactly as the brief specifies; the default compiler search order in the script is - `$CXX, clang++-20, clang++, c++, g++` as written. -6. **Cosmetic YAML.** The CI job got a blank line between `runs-on:` and `steps:` and a longer - comment block than the brief's snippet, to match the voice and spacing of the neighbouring - `flatc-check` / `benchmark` jobs. Steps and commands are verbatim from the brief. - -No other deviation. `--fail-on-added-bytes` is accepted and documented as reserved (spec says -"reserved for a future hard gate"), `Allow` entries require a printed `Reason`, and the -`--json` / `--markdown` / `--only-names` / `--threshold` / `--rename-map` flags are all implemented. - -## 4. Unfinished / handed to the integrator - -- **The `--require-all` ratchet (C.3) is deliberately not flipped.** Both call sites - (`MobileGL/MG_Test/Purity/CMakeLists.txt` `add_test` COMMAND and the `include-graph-check` step - in `test.yml`) are one appended flag away; verified today that the flag produces exit 1 with a - message naming both headers. -- **Probes A and B have never run against a real header** — they cannot, in this tree. Their - forbidden lists, the `glslang::` ≤ 2 text limit, self-test controls 2 and 3 and the - "third `glslang::` token" text-limit control are all coded and arm themselves automatically the - moment `MGPipeValueTypes.h` / `ProgramArtifacts.h` appear. The integrator should see A flip to OK - after merging `p05/value-types` and B after `p05/program-artifacts` (C.1), and should treat an - all-SKIP run at that point as a failure. -- **CI has not run** (D10): `feat/disaggregated` is not a push trigger, so the new job needs - `gh workflow run test.yml --ref feat/disaggregated` from the integrator, who records the URL. -- **The `gl44to46` caselist run** owed by `ROADMAP.md:34` is device time and out of this package. -- Nothing was pushed; both commits are local on `p05/include-gate`. diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-review-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-review-v1.md deleted file mode 100644 index 739bb57e..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-review-v1.md +++ /dev/null @@ -1,114 +0,0 @@ -# p05-program-artifacts — adversarial review v1 - -Reviewed: tree `~/w7/p05-program-artifacts`, branch `p05/program-artifacts`, HEAD `77beb7d6` (3 commits on `6672778b`: `a180dc57`, `3ce1264c`, `77beb7d6`). -Against: `BRIEF-P05.md` (A, B.0, B.2, C, D) and `p05-results/program-artifacts-v1.md`. -Every command below was re-run by me in WSL Arch from the package tree (scripts `scratchpad/wf3/p05-review-pa/s1..s10.sh`, raw outputs under `~/w7/rv-pa/`). `~/w7/pipe` was used read-only (`ctest -N`, `nm`, one run of one existing integration binary). The tree was left exactly as found: `git status --short` (excluding `build-linux/`) empty before and after, HEAD unchanged, `~/w7/pipe` clean. - -**Verdict: APPROVED — 0 majors, 6 minors.** The package is a pure move that meets the brief; every claim in the result file that matters reproduced, and every gate it adds goes red for its reason. - ---- - -## 1. What I tried to refute, and what I found - -### 1.1 "Pure move" — copied vs moved, verbatim bodies, semantics - -| check | command | observed | -|---|---|---| -| Struct bodies verbatim | `git show 6672778b:ProgramObject.h` regions `:41-104`, `:1144-1171`, `:1173-1449` vs `git show a180dc57:ProgramArtifacts.h` `:28-400`, both with leading whitespace stripped and blank lines dropped, `diff` | 7 hunks, all declared: 5 comment lines `glslang::X` → `glslang X` (deviation 1 in the result file), the one added I5 sentence the brief itself prescribes (B.2 layout), and the closing `} // namespace` line. **No field added, removed, reordered, re-typed, no default changed.** | -| ODR: one definition per name | `grep -rn "^\s*struct \(TypeFacts\|ResourceReflection\|XfbVarying\|LinkArtifacts\|SpirvArtifacts\)\b" MobileGL` | exactly 5 hits, `ProgramArtifacts.h:31,63,95,160,359` | -| `kInvalidUniformOffset` one definition | `grep -rn "kInvalidUniformOffset\s*=" MobileGL` | `ProgramArtifacts.h:26` (`inline constexpr Uint = ~0u`) and `ProgramObject.h:496` defined *from* it; the old `= ~0u` in class is gone | -| Alias identity actually enforced | scratch farm `~/w7/rv-pa/neg4f`: replaced the `using TypeFacts = …` alias in a copy of `ProgramObject.h` with a second `struct TypeFacts {…}` body, compiled `ProgramArtifactsTest.cpp` by hand with the farm first on the include path | `ProgramArtifactsTest.cpp:29: error: static assertion failed … is_same_v` (plus `ProgramObject.h:464` no-viable-conversion). The copy-left-behind failure the brief's risk table names is caught at compile time. | -| Behaviour unchanged (strongest evidence) | own `symcmp.py` (`nm --defined-only -S` + `c++filt`, fold `GLState::ProgramObject::{5 names}` → `GLState::X`, multiset compare) on `~/w7/pipe/build-linux/libMobileGL.so` (built 22:06 at `6672778b`, commit time 22:13 — same tree state) vs `build-linux/libMobileGL.so` | `symbols 30511 -> 30511; 43 names folded on the before side, 0 on the after side; added 0 removed 0 resized 1`; `.text 10792579 -> 10792579`, `.rodata`, `.data`, `.bss`, `Total 17209819` all byte-identical. The 1 resized is `typeinfo name for std::_Sp_counted_ptr_inplace<…LinkArtifacts,…>` 122 → 107 bytes: the RTTI name *string* lost `13ProgramObject`. Fully attributed. Byte-identical `.text` also rules out any ADL/overload-resolution shift from de-nesting. | -| No mangled-name dependence anywhere | `grep -rn "typeid\|type_info\|__PRETTY_FUNCTION__\|__FUNCSIG__" MobileGL` (non-test) | empty | -| B.0 D5 (glslang members verbatim, count pinned) | `grep -o "glslang::" ProgramArtifacts.h \| wc -l` (occurrences, not lines) | `2` — `:165 SharedPtr program;`, `:232 Vector uniformInitialValues;` | -| B.0 D6 (includes) | `grep -n "#include" ProgramArtifacts.h` | `:10 `, `:15 ` — nothing else | -| B.0 D7 (sizes) | header `:179-203`; test prints `sizeof: TypeFacts=44 ResourceReflection=128 XfbVarying=128 LinkArtifacts=1056 SpirvArtifacts=88` | TypeFacts pinned unconditionally at 44 with `is_trivially_copyable_v`; the four container-bearing sizes under `__GLIBCXX__ && !_GLIBCXX_DEBUG && SIZE_MAX == UINT64_MAX`; libc++ `#elif` inert behind `MGL_ARTIFACT_SIZES_LIBCXX_PINNED`; MSVC unasserted. Exactly the brief's shape. | -| I5 untouched | `git diff --stat 6672778b..HEAD` | only 4 files: `ProgramArtifacts.h` (new), `ProgramObject.h`, `MG_Test/Program/CMakeLists.txt`, `ProgramArtifactsTest.cpp`. `MG_Backend/**`, `MG_Impl/**`, `ProgramObject.cpp`, `ProgramLinkTask.*`, `ProgramSpirvTask.*` untouched; `m_artifacts`/`m_spirv` and `Artifacts()`/`Spirv()` not in the diff. | -| Includers of `ProgramObject.h` | repo-wide grep (both spellings) | the 8 non-self includers the brief lists + `ProgramObject.cpp` + the new test; none edited; all compile through the in-class aliases. | - -### 1.2 Tests exist, run under the right label, and can fail - -| check | command | observed | -|---|---|---| -| Incremental build | `cmake --build build-linux -j 16` (`CCACHE_BASEDIR=/home/swung/w7`) | `ninja: no work to do.` | -| Unit suite | `ctest --test-dir build-linux -L unit --no-tests=error -j 12` | `100% tests passed, 0 tests failed out of 1463`; 3 `***Skipped` (`LogLevel.DebugBuildKeepsEverything`, `LogLevel.AssertIsLiveInDebugBuilds`, `XfbFrontendOrderInvarianceTest.DumpBSpirvForDisassembly`) — skip-by-design, same as base | -| No test name lost | `ctest -N` (pattern `'^\s+Test\s+#'` — the implementer is right that the brief's single-space pattern drops tests < #1000) on both trees, `comm -23 before after` | before 2326, after 2331, **lost: none**, added: exactly `ProgramArtifacts.{AliasesAreTheSameTypes,SizesArePinnedOnThisToolchain,TypeFactsIsPodOf44Bytes,VisitFieldsCoversEveryMember,VisitFieldsPassesConstnessThrough}` | -| Label reaches `ctest -L unit` | `ctest -L unit -N \| grep -c ProgramArtifacts` | `5` | -| CMake wiring executes | `MG_Test/CMakeLists.txt:85 add_subdirectory(Program)` unconditional; stanza `Program/CMakeLists.txt:280-297` = the `AsyncCompileTest` shape with `${LINK_LIBRARIES}`, `gtest_discover_tests(… DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit)`; CI `test` job configures `-DMOBILEGL_BUILD_TEST=ON` (`test.yml:86`) and runs `ctest -L unit` | wired, discovered, runs | -| **Red-for-its-reason C1** — member added without a table entry | scratch copy `~/w7/rv-pa/neg1/…/ProgramArtifacts.h` with `Int addedWithoutATableEntry = 0;` appended to `LinkArtifacts`; `clang++ -I~/w7/rv-pa/neg1 -std=gnu++23 -Iinclude -IMobileGL … -fsyntax-only probe_pa.cpp` | exit 1: `error: static assertion failed … 'sizeof(LinkArtifacts) == 1056': LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) …` / `note: expression evaluates to '1064 == 1056'` | -| **Red-for-its-reason C2** — table entry dropped | symlink farm `~/w7/rv-pa/neg2f` (`cp -rs MobileGL`, header replaced by a copy minus `v("linkStatus", a.linkStatus);`); compiled `ProgramArtifactsTest.cpp` with its exact `compile_commands.json` line + `-I` first, linked with the ninja link line, ran | `ProgramArtifactsTest.cpp:84: Failure … Which is: 56 / Which is: 57`, plus `:110` (`linkStatus` write-through) fails; exit 1. Same hand pipeline on the **unmodified** header (`~/w7/rv-pa/pos2f`): `[ PASSED ] 2 tests` — so the failure is the dropped entry, not the pipeline. | -| **Red-for-its-reason C3** — purity | farm `~/w7/rv-pa/fake2` with `#include "ShaderObject.h"` inserted at header `:15`; (a) manual `-H` probe with the brief's grep; (b) the real gate from `~/w7/p05-include-gate/scripts/check_include_closure.py` copied under the farm's `scripts/` | (a) hits: `MobileGL/Config.h`, `MG_Backend/BackendObject.h`, `MG_Backend/BackendObjects.h`, `…/BackendObject_DirectGLES.h`, `…/BackendObject_DirectVulkan.h`, `BufferState/BufferObject.h`; (b) `artifacts-header FORBIDDEN 89 headers in closure, 16 forbidden (mode=text)` / `FORBIDDEN 678 headers, 16 forbidden (mode=clang)`, chain printed one hop per line, `::error::…`, exit 1 | -| Table completeness vs struct bodies (independent of the counting test) | python cross-check: member names parsed from each struct body vs `v("name", a.name)` entries | TypeFacts 20/20, ResourceReflection 14/14, XfbVarying 11/11, LinkArtifacts members 58 / table 57 (**only `program` missing**), SpirvArtifacts 8/8; every `"name"` string equals its member, every table in declaration order | - -### 1.3 The closure gate, on the real tree - -The gate lives in `p05/include-gate`; I ran that script (copied, unmodified) against this tree through a symlinked root `~/w7/rv-pa/fake/{MobileGL,include,3rdparty → tree}` so the tree itself was not touched: - -``` -include-closure: value-header SKIP header not present yet: MobileGL/MG_Pipe/MGPipeValueTypes.h -include-closure: artifacts-header OK 65 headers in closure, 0 forbidden (mode=text, cxx=-) -include-closure: artifacts-header OK 653 headers in closure, 0 forbidden (mode=clang, cxx=/usr/sbin/clang++) -include-closure: artifacts-header SELF-CONTAINED OK (-fsyntax-only, cxx=/usr/sbin/clang++) -include-closure: wire-header OK … -include-closure: self-test control-3 (ShaderObject.h added back to the artifacts header's TU) [text]: tripped, 16 violation(s) … [clang]: tripped, 16 violation(s) … -include-closure: self-test text limit: tripped as expected (glslang:: occurs 3 times, budget is 2) -include-closure: 3 probes, 1 skipped, 0 problem(s) exit 0 ---require-all: exit 1 (value-header absent — expected until p05/value-types lands) ---probe artifacts-header alone: OK/OK/SELF-CONTAINED OK, exit 0 -``` -Manual equivalent (brief B.2): `-H -E` probe → 653 closure lines, 0 matches for `ShaderObject.h|SpvcSession.h|MG_Util/ShaderTranspiler/|MobileGL/Config.h|MobileGL/MG_Backend/|BufferState/`; the only `MobileGL/` headers in the closure are `Defines.h`, `Includes.h`, `ProgramArtifacts.h`, `MG_Util/Debug/Log.h`, `MG_Util/GLExtensions.h`, `MG_Util/PlatformStubs.h`, `MG_Util/Types.h`. `-fsyntax-only` OK. `python3 scripts/gen_pipe.py --check` → `generated files are up to date`. - -### 1.4 Lost transitive includes — TUs this configuration never compiled - -`ProgramObject.h` sits behind `Core.h`, so commit 2 (dropping `SpvcSession.h`, which forwards `` and `MG_Util/ShaderTranspiler/Types.h`) affects every `Core.h` consumer. The local build has `MOBILEGL_BUILD_BENCHMARK=OFF`; **CI's `test` job has it ON (`test.yml:87`)**, so the implementer's "no consumer needed an include" was proven only for the TUs in `compile_commands.json`. I enumerated every `.cpp` under `MobileGL/ tools/ android-plugin/` absent from `compile_commands.json` (48 files: benchmarks, MG_Remote transport, CGL/WGL/NSOpenGL, Wire tests, JNI, tools) and syntax-checked the ones that reach the program headers with the exact flag set of a compiled test TU plus `_deps/benchmark-src/include`: - -``` -OK MobileGL/MG_Benchmark/Buffer/BufferBench.cpp -OK MobileGL/MG_Benchmark/Container/UnorderedMapBench.cpp -OK MobileGL/MG_Benchmark/Program/ProgramBench.cpp -OK MobileGL/MG_Benchmark/SanityBench.cpp -OK MobileGL/MG_Benchmark/ShaderCache/TranslationCacheBench.cpp -OK MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp -OK MobileGL/MG_Util/Converters/SPIRVCrossToGL/SpvcTypeConverter.cpp (referenced by no CMakeLists; checked anyway) -``` -Repo-wide, every TU that names `SpvReflect*`/`SpvcSession`/`SpvcMetadata` either includes `SpvcSession.h`/`spirv_reflect.h` itself or is in this build's compile set (and built). `MG_Test/Backend/DirectVulkan/SanityTest.cpp` fails only on `GLFW/glfw3.h` (not installed; unrelated). `DriverBenchJni.cpp` names none of those symbols. Conclusion: commit 2 is safe for the CI configuration as well; the binding check remains the integrator's `gh workflow run` (C.5). - -### 1.5 The integration-gpu failure the result file reports - -`DirectVulkan.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput`: one run each of the untouched base binary (`~/w7/pipe/build-linux/MobileGL/MG_IntegrationTest/MobileGLIntegrationTest`, DirectVulkan + lavapipe env as the ctest property injects) and the package's: **both `[ FAILED ]`**. Pre-existing on this machine, as the implementer's 3×3 A/B said; with byte-identical `.text` it cannot be this package. - ---- - -## 2. Majors - -None. - -## 3. Minors (none blocks; listed for the integrator / a follow-up commit) - -1. **File mode.** `MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp` is committed as `100755`; its 15 siblings in that directory are `100644` (`git ls-files -s MobileGL/MG_Test/Program/`). Cosmetic; `git update-index --chmod=-x` in the integrator's merge or a follow-up. -2. **Test/header `#if` drift risk.** `ProgramArtifactsTest.cpp:297` re-spells the header's guard (`__GLIBCXX__ && !_GLIBCXX_DEBUG && SIZE_MAX == UINT64_MAX`) instead of `#ifdef MGL_LINKARTIFACTS_SIZE`. The brief literally asked for "under the same `#if`", so this is by the letter — but when C.4 pins the libc++ branch, the header will assert on Android while the runtime twin silently records `sizes_pinned: no` there. Recommend `#ifdef MGL_LINKARTIFACTS_SIZE` in the test (one-line change, integrator-owned since C.4 touches the header anyway). -3. **Macro leak.** `MGL_RESOURCEREFLECTION_SIZE`/`MGL_XFBVARYING_SIZE`/`MGL_LINKARTIFACTS_SIZE`/`MGL_SPIRVARTIFACTS_SIZE` (`ProgramArtifacts.h:187-190`) are `#define`d in a header reached by every `Core.h` consumer and never `#undef`'d. No collision today (`grep -rn MGL_.*_SIZE` shows only `MGL_RESIDUAL_BLOCK_SIZE`); the brief prescribed this shape. Note only. -4. **Transitive-include reliance inside the header.** `std::same_as` (``), `std::remove_const_t` (``), `SIZE_MAX`/`UINT64_MAX` (``) are not included by the header; D6 pins the include list, and the `-H` closure shows `concepts` and `cstdint` arrive via `` on libstdc++ (`-dM -E` confirms `SIZE_MAX`, `UINT64_MAX` defined). Two things worth knowing: (a) if `SIZE_MAX`/`UINT64_MAX` were ever *not* defined at that point, the preprocessor evaluates `0 == 0` → true, i.e. the guard fails open rather than closed; `-Wundef` is not in the project flags. (b) libc++ and MSVC supply `std::same_as` through ``/`` in C++20 mode in practice. Not a defect under D6; a ``/`` pair would cost nothing and is not forbidden by the gate — the plan owner's call. -5. **Result-file correction (deviation 6).** The implementer warns that the brief's C.4 recipe `--strip-scope 'MobileGL::MG_State::GLState::ProgramObject::'` would report "43 added / 43 removed". With the real `scripts/symbol_report.py` from `p05/include-gate` it does not: `--strip-scope 'MobileGL::MG_State::GLState::ProgramObject::'` → `27799 -> 27799 defined symbols: 0 added, 0 removed, 1 resized, 42 renamed`, identical to the `--rename-map` fold and to `--strip-scope 'ProgramObject::'`. The warning was true of the implementer's hand-rolled `symcmp.py`, not of the script the integrator will run. The C.4 recipe can be used as written; the expected line is `0 added, 0 removed, 1 resized, 42 renamed` with the one resized = the RTTI name string (`-15` bytes). -6. **Cosmetic.** `ProgramArtifacts.h:18` starts a sentence in lowercase ("…asserts that closure. the glslang scope token appears…"). - -## 4. Deviations declared by the implementer — verified - -| # | claim | verdict | -|---|---|---| -| 1 | 5 comment respellings `glslang::X` → `glslang X` | exactly those 5 lines differ (§1.1 verbatim diff); no code token changed; needed because the gate counts occurrences in the whole file (the include-gate self-test confirms "budget is 2") | -| 2 | LinkArtifacts 57 of 58, not 52 of 53 | independent count 58 members (§1.2 cross-check); the brief's 53 was stale | -| 3 | `ctest -N` grep pattern | correct; used here | -| 4 | extra test `VisitFieldsPassesConstnessThrough` | additive; fails when the table is broken (§1.2 C2 also tripped it at `:110`) | -| 5 | CMake stanza appended at file end | content matches the `AsyncCompileTest` shape; wired (§1.2) | -| 6 | `symbol_report.py` `--strip-scope` warning | **incorrect for the real script** — see minor 5 | - -## 5. Not verified here (by design) - -- The libc++/NDK sizes (integrator C.4) and the MSVC build; nothing in the package is platform-specific beyond the per-STL `#if`. -- The full `integration-gpu` suite (only the reported failure was A/B'd, §1.5). -- CI dispatch of `test.yml` on `feat/disaggregated` (D10, integrator). - -## 6. State of the trees after review - -`~/w7/p05-program-artifacts`: HEAD `77beb7d6`, `git status --short` (minus `build-linux/`) empty, `build-linux/` rebuilt to no-op. `~/w7/pipe`: untouched, `6672778b`, clean. All scratch work under `~/w7/rv-pa/` (farms `fake`, `fake2`, `neg1`, `neg2f`, `pos2f`, `neg4f`, logs) and `scratchpad/wf3/p05-review-pa/`. diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-v1.md deleted file mode 100644 index f58f4307..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/program-artifacts-v1.md +++ /dev/null @@ -1,66 +0,0 @@ -# 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 `` and ``; 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 `. `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, 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 && 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: `` (line 10), `` (line 15) | -| 10 | `grep -c "glslang::" MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` | `2` (lines 165 `SharedPtr program;` and 232 `Vector uniformInitialValues;`) | -| 11 | Closure probe: `printf '#include \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, _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). diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-review-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-review-v1.md deleted file mode 100644 index 023f12df..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-review-v1.md +++ /dev/null @@ -1,83 +0,0 @@ -# p05-value-types — adversarial review v1 - -Reviewed: `~/w7/p05-value-types`, branch `p05/value-types`, HEAD `92e925f9` (one commit on `6672778b`). -Reference: `~/w7/pipe` @ `6672778b` (read-only; untouched — `git status` clean apart from its build dirs). -Review scripts: `scratchpad/wf3/p05-review-vt/s1..s6.sh`; logs under `~/w7/logs/review-vt/`. -Tree state at exit: `git status --short` prints only `?? build-linux/` (identical to the implementer's state); the temporarily copied gate script was removed; the red-for-its-reason experiments were done on out-of-tree copies (`~/w7/logs/review-vt/red*/`), never in the tree. - -**Verdict: APPROVED — 0 majors, 3 minors.** The claim "correct, complete, pure move meeting the brief" survived every attempt to refute it. - ---- - -## 1. What I tried to refute, and what I observed - -### 1.1 "Cut, not copy" — verbatim-move proof (s2) - -Reconstructed the moved regions from the base commit and diffed them against the new header: - -``` -git show 6672778b:.../RenderState.h | sed -n '15,370p' -> old_rs.txt -git show 6672778b:.../SamplerObject.h | sed -n '14,97p' -> old_so.txt -git show 6672778b:.../VertexArrayObject.h | sed -n '17,70p' -> old_vao.txt -``` -- Non-vertex part (`old_rs + old_so`, with the two `MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS` → `kMGMaxDrawBuffers` respellings applied) vs `MGPipeValueTypes.h:32-471`: `diff -B` **exit 0** — byte-exact including indentation and comments. -- Vertex part (`old_vao` de-indented by 4) vs `MGPipeValueTypes.h:474-529`: `diff -B` **exit 0**. -- Whole region `diff -w -B`: the only extra lines are the three the brief prescribes (`namespace MG_State::GLState {`, `class BufferObject;`, closing brace). -- Banner `MGPipeValueTypes.h:1-7` == `MGPipeTypes.h:1-7` with the path substituted (`banner-ok`). -- Deleted "extra" lines (`RenderState.h:371`, `SamplerObject.h:97`, `VertexArrayObject.h:71`) are all blank (`cat -A` shows `$`), so deviation 1 of the result file is whitespace-only as declared. - -Member order, defaults, enum underlying types (`BorderColorForm : Uint8` at `MGPipeValueTypes.h:441`), `constexpr`-ness (`MAX_VIEWPORTS` at `:244`), namespaces (`MG_State::GLState` reopened at `:473`, D3) — all unchanged by construction of the diff above. - -### 1.2 ODR — no second definition anywhere - -- Brief grep (8 structs + 3 enums) over `MobileGL/`: **11 hits, all in `MobileGL/MG_Pipe/MGPipeValueTypes.h`** (lines 32, 168, 208, 219, 229, 239, 441, 447, 476, 517, 525). -- Extended grep for **all 17 moved enums** over `MobileGL tools android-plugin` (`*.h *.hpp *.cpp *.inc`, build dirs excluded): 17 hits, all in the new header. -- `MAX_DRAW_BUFFERS\s*=`: exactly one, `FramebufferObject.h:107` `= MobileGL::kMGMaxDrawBuffers` (D4). `kMGMaxDrawBuffers` appears 8 times (header, tests, FramebufferObject.h); none in `MG_Backend`. - -### 1.3 Purity / closure (s3, s4) - -- Text: `grep -n "MG_State/\|MG_Backend/\|MG_Impl/\|MG_Remote/" MGPipeValueTypes.h` → **empty** (exit 1). -- Includes of the four forwarding headers: `RenderState.h:10-11` = `` + `` only; `SamplerObject.h:10-11` same; `VertexArrayObject.h:10-13` keeps `BufferObject.h`/`Types.h` + value header; `FramebufferObject.h:14` adds the value header. -- Compiler closure, brief's exact flags, `-H -E` on `#include `: **654 lines, 0 under `MobileGL/MG_State/ | MG_Backend/ | MG_Impl/ | MG_Remote/`**. `MobileGL/` headers in the closure: `Defines.h, Includes.h, MG_Pipe/MGPipeValueTypes.h, MG_Util/{Debug/Log.h, GLExtensions.h, Math/VectorTypes.h, PlatformStubs.h, Types.h}`. `-fsyntax-only` exit 0 (self-contained). -- **Real gate run** (copied `~/w7/p05-include-gate/scripts/check_include_closure.py` into `scripts/` for the run, removed afterwards; `REPO_ROOT` is derived from the script location so it had to be in-tree): - - `--mode both --self-test`: `value-header OK 66 headers (text)`, `OK 654 headers (clang)`, `SELF-CONTAINED OK`; `artifacts-header SKIP` (expected, other package); `wire-header OK`; `self-test: 4 negative-control trip(s), parser checks OK`; `3 probes, 1 skipped, 0 problem(s)`, exit 0. The 4 trips = self-test steps 1 and 2 (step 2 is the roadmap's literal "add an `MG_State` include back and it goes red", which only runs once `MGPipeValueTypes.h` exists — it does now, and it trips) in both modes. - - `--require-all`: exit 1, names only the missing `ProgramArtifacts.h` (D9 semantics correct; the value header itself is not the reason). - - `--probe value-header`: `1 probes, 0 skipped, 0 problem(s)`. -- **Red-for-its-reason, by hand** (out-of-tree shadow copy at `~/w7/logs/review-vt/red/MG_Pipe/MGPipeValueTypes.h` with `#include ` inserted after `:15`, put first on the `-I` path): the same `-H` probe reports **1 forbidden line**. Restored = nothing to restore; the tree was never edited. -- **Tripwire fires**: shadow copy with `sizeof(RenderStateParameters) == 1` → `error: static assertion failed ... note: expression evaluates to '1168 == 1'`. - -### 1.4 Behaviour unchanged (s3) - -- `cmake --build build-linux -j 16`: `ninja: no work to do` (the implementer's build is current). -- `ctest -L unit --no-tests=error -j 8`: **100% passed, 0 failed of 1460**, 11.0 s. The two new tests ran inside the `-L unit` selection (`#1053`, `#1054` Passed) — they are labelled, not orphaned. -- `ctest -N` names, `comm -23 before after`: **lost: none**; added exactly `PipeCatalogue.ValueTypeLayoutsArePinned`, `PipeCatalogue.ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail` (1327 → 1329). -- `python3 scripts/gen_pipe.py --check`: `generated files are up to date`, exit 0; tree still clean afterwards. -- `nm --defined-only -S` keyed on mangled name (type+size): **30511 → 30511, diff empty**; with addresses included: diff empty. `size --format=sysv`: `.text 10792579` and `Total 17209819` on both sides. `objcopy --only-section=.text` + `cmp`: **`.text` byte-identical**. The baseline `.so` carries a `GIT@a4baa9d` configure stamp (implementer already attributed it); since `.text` is byte-identical to a build known to be at `6672778b`+header-move, the baseline is a valid `6672778b` build. -- `grep -rc "pGLContext" MobileGL/MG_Backend | sort` identical to pipe; `git diff --stat 6672778b -- MG_Backend MG_Impl MG_Util` empty. - -### 1.5 Lost transitive includes on platforms that were not built - -- 27 files include the touched headers; every `.cpp` among them (8) is in `build-linux/compile_commands.json`. -- 31 `.cpp` under `MobileGL/` are not built on Linux (Android JNI, WGL/CGL/NSOpenGL, MG_Remote split-only, benchmarks, a dead `VkTextureSamplerManager.cpp` referenced by no CMake file). Inspected their includes: none includes `RenderState.h`/`SamplerObject.h`/`VertexArrayObject.h` directly; the JNI ones go through `MG_Impl/GLImpl/*` and `Includes.h` (same path the Linux build exercises). The 18 split-only TUs (`~/w7/pipe/build-linux-split`) are all `MG_Remote/Transport/*` and `MG_Test/Wire/*`, which by the wire-header rule do not include `Includes.h` at all. No plausible Android/Windows/split fallout found. - -### 1.6 Wiring, formatting, docs - -- No CI format gate exists (`grep clang-format .github/workflows/*.yml` empty); `clang-format --dry-run` on the new header nevertheless reports **0 warnings** (clang-format 22.1.6, repo `.clang-format`). No trailing whitespace in the diff (`grep -P '^\+.*[ \t]+$'` count 0); LF endings, newline at EOF, mode 100644. -- `scripts/check_doc_citations.py` (base tree, the `test.yml:853-861` lint, which is `|| true` anyway) on the three `docs/Disaggregated/*.md`: `19 citations ... 0 problem(s)`. `MGPipeTypes.h` line numbers are unchanged (the comment rewrite is line-for-line), so `ARCHITECTURE.md:359`'s `MGPipeTypes.h:535` still points at `MGL_RESIDUAL_BLOCK_SIZE`. -- Commit message: prescribed subject verbatim + `- ` bullets explaining why; no attribution lines; not pushed; no fixture binaries touched. -- Deviations 1-7 in the result file were each checked and are as declared (whitespace, comment wording, additional-only test expectations, tooling not present in this tree). - -## 2. Majors - -None. - -## 3. Minors - -1. **`MGPipeTypes.h:16-18` overstates the repaid debt.** The rewritten comment says "this header no longer reaches into MG_State". The `-H` closure of `#include ` still contains `.. MobileGL/MG_Backend/BackendObject.h` → `... MobileGL/MG_State/GLState/TextureState/TextureEnum.h` (`BackendObject.h:11`), exactly the D1 reason the brief gives for not moving `DynamicBackendParameters`. The following sentence ("that one include is what still keeps purity gate A off this header") is correct, so nothing functional depends on it, but the first sentence should read "no longer includes MG_State directly; the remaining MG_State reach is `TextureEnum.h` via `BackendObject.h`". Comment-only; fix in the integrator's docs/comment pass or a follow-up, not a reason to hold the package. -2. **`PipeCatalogue.ValueTypeLayoutsArePinned` is tautological on the CI toolchain** (`PipeCatalogueTest.cpp:117-135`): every `EXPECT_*` compares the same compile-time constants the header's `static_assert`s already pin, so on Linux/libstdc++ it cannot fail without the build already failing. This is exactly what the brief asked for ("runtime twins ... so the numbers show up in ctest output on every platform"), and it does earn its keep on MSVC/NDK where a static assertion might be `#if`'d out later; recorded so nobody counts it as an independent gate. -3. **Result-file line range `SamplerObject.h:14-96` vs brief `14-97`**: line 97 at `6672778b` is blank, so the cut is the brief's cut; only the description differs. No action. - -## 4. Notes for the integrator (not findings against this package) - -- The package's `build-linux/CMakeCache.txt` has `MOBILEGL_ITEST_VK_ICD=` empty (implementer's Program203 diagnosis checks out against `MG_IntegrationTest/CMakeLists.txt:284-298`); reconfigure the three p05 build dirs with the lavapipe ICD before relying on a plain `ctest -L integration-gpu`. -- With this package merged after include-gate, `check_include_closure.py --mode both --self-test` already reports `value-header OK` in both modes plus the step-2 negative control tripping; only `artifacts-header` remains `SKIP` until program-artifacts lands. diff --git a/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-v1.md b/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-v1.md deleted file mode 100644 index 43941cf1..00000000 --- a/docs/Disaggregated/notes/recovered/wf3/p05-results/value-types-v1.md +++ /dev/null @@ -1,63 +0,0 @@ -# p05-value-types — result v1 - -Tree `~/w7/p05-value-types`, branch `p05/value-types`, base `feat/disaggregated @ 6672778b`. -Build dir `build-linux/` (Release, `/usr/sbin/clang++` clang 22, ccache, tests + integration tests ON). -Logs: `~/w7/logs/p05vt-*.log`, `~/w7/logs/nm-*.raw`, `~/w7/logs/probe_vt_H.txt`, `~/w7/logs/p203-*.log`. - -## Commits (one, as the brief asks) - -- `COMMIT_HASH` `[Refactor] (Pipe, State): extract MGPipeValueTypes.h - move the render-state, sampler and vertex value types and their enums out of MG_State/GLState so MG_Pipe no longer reaches RenderState.h; pure move, member order and namespaces unchanged` - -Files: `MobileGL/MG_Pipe/MGPipeValueTypes.h` (new, 550 lines), `MobileGL/MG_Pipe/MGPipeTypes.h`, `MobileGL/MG_Pipe/generated/PipeVerify.inc`, `MobileGL/MG_State/GLState/RenderState/RenderState.{h,cpp}`, `MobileGL/MG_State/GLState/SamplerState/SamplerObject.h`, `MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h`, `MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h`, `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp`, `scripts/gen_pipe.py`. Nothing under `MG_Backend/`, `MG_Impl/`, `MG_Util/` touched. Not pushed. - -## What was done (B.1, in order) - -1. `MGPipeValueTypes.h` created by a Python script (`scratchpad/wf3/p05-value-types/cut.py`) that slices the exact line ranges the brief names, with the anchor line of every range asserted before the cut: `RenderState.h:15-189` (11 enums, D2), `:191-370` (4 structs; `:263`/`:273` respelled to `kMGMaxDrawBuffers`), `SamplerObject.h:14-96` (6 enums + `SamplerParameters`), `VertexArrayObject.h:17-70` (3 vertex types, re-indented by one level because they now sit in `namespace MG_State::GLState` inside `namespace MobileGL` instead of three nested namespaces; namespace unchanged, D3; `class BufferObject;` forward-declared). `inline constexpr Uint kMGMaxDrawBuffers = 8;` (D4). Macro guard + `#pragma once`; includes exactly ``, ``, ``, ``. Trip wires as in the brief layout; the two "measure" numbers came out as `SamplerParameters` 100 and `VertexAttributeVersion` 6 (the build accepted them first time; `is_standard_layout_v` holds, so that assertion is kept). -2. Forwarding edits: `RenderState.h` now includes `` + `` only; `RenderState.cpp` gained the direct `FramebufferObject.h` include (it spells `FramebufferObject::MAX_DRAW_BUFFERS` six times); `SamplerObject.h` and `VertexArrayObject.h` include the value header and keep only their classes; `FramebufferObject.h` includes the value header and defines `MAX_DRAW_BUFFERS = MobileGL::kMGMaxDrawBuffers`. -3. `MGPipeTypes.h:33` → `#include "MGPipeValueTypes.h"`, `:32` kept (D1), debt comment rewritten to say the MG_State half is repaid and `DynamicBackendParameters` is what still keeps gate A off `MGPipeTypes.h`; `using` comment updated. `gen_pipe.py:163-165` and the MEMCMP FALLBACK comment (`:410-414`, the source of `PipeVerify.inc:232-238`) reworded to P1; `python3 scripts/gen_pipe.py` run and the regenerated `.inc` committed (comment-only diff). -4. Full build: **zero lost-transitive-include failures** — no consumer `.cpp` needed a direct include (the candidates all reach `FramebufferObject.h` through `Core.h`, as the brief predicted). -5. Tests `PipeCatalogue.ValueTypeLayoutsArePinned` and `PipeCatalogue.ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail` added to `PipeCatalogueTest.cpp`; **no include added** (`offsetof` reaches it through `MGPipe.h → MGPipeTypes.h → MGPipeValueTypes.h → `). - -## Verification (every command, actual result) - -| # | command | result | -|---|---|---| -| 1 | `cmake --build build-linux -j 16` (three times: after the cut, after the tests, after a comment reword) | exit 0 each time; first build 237 steps, `libMobileGL.so` + all test binaries link | -| 2 | `ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -j 8` | **100% tests passed, 0 failed out of 1460** (log `p05vt-unit.log`; rerun after the final rebuild: see `p05vt-unit2.log`, UNIT2_RESULT) | -| 3 | new tests present in the run | `#1053 PipeCatalogue.ValueTypeLayoutsArePinned Passed`, `#1054 PipeCatalogue.ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail Passed` | -| 4 | `ctest -N` name lists, `comm -23 before after` (`~/w7/pipe/build-linux` vs mine) | before 1327 / after 1329 names; **lost: none**; added: the two tests above | -| 5 | ODR grep (`^\s*struct (RenderStateParameters\|…)\b \| ^\s*enum class (BlendFactor\|BorderColorForm\|CapabilityInput)\b` over `MobileGL/`) | 11 hits, **all in `MobileGL/MG_Pipe/MGPipeValueTypes.h`** (lines 32, 168, 208, 219, 229, 239, 441, 447, 476, 517, 525); no second definition anywhere | -| 6 | `grep -n "MG_State/\|MG_Backend/\|MG_Impl/\|MG_Remote/" MGPipeValueTypes.h` | **empty** (after rewording my own PURITY comment, which had matched with a slash spelling) | -| 7 | `grep -n "#include" RenderState.h` | `10:#include `, `11:#include ` only | -| 8 | closure probe: `clang++ -std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -H -E` on `#include ` | 654 closure lines, **0 under `MobileGL/MG_State/`, `MG_Backend/`, `MG_Impl/`, `MG_Remote/`** | -| 9 | same flags, `-fsyntax-only` on the probe TU | exit 0 (header is self-contained) | -| 10 | `git diff --stat 6672778b -- MobileGL/MG_Backend` | **empty** | -| 11 | `grep -rc "pGLContext" MobileGL/MG_Backend \| sort` before (pipe) vs after | **identical** | -| 12 | `python3 scripts/gen_pipe.py --check` | `generated files are up to date`, exit 0 | -| 13 | symbol report — `scripts/symbol_report.py` does not exist in this tree (it is the include-gate package's deliverable), so the equivalent was done by hand: `nm --defined-only -S` on `~/w7/pipe/build-linux/libMobileGL.so` vs `build-linux/libMobileGL.so`, keyed on mangled name, comparing type+size | 30511 → 30511 defined symbols; **0 added / 0 removed / 0 resized; 0 symbols changed address**; `size --format=sysv`: `.text` 10792579 B on both sides, Total 17209819 on both | -| 14 | attribution of every differing byte between the two `.so` files (`cmp -l`, mapped to sections) | 31 bytes total, length equal: 20 B in `.note.gnu.build-id`; 7 B in `.rodata` = the `GIT@a4baa9d` → `GIT@6672778` build stamp string (the reference build carries an older configure-time stamp); 4 B in `.rodata` = SPIRV-Tools' `Timestamp: 2026-09-05T22:05:41` → `22:36:04` string. `.text`, `.rodata` otherwise, `.data`, `.data.rel.ro`, `.eh_frame`, `.init_array`, `.dynsym`, `.dynstr`, `.gnu.hash` byte-identical (`objcopy --only-section` + `cmp`). Section table identical. **Zero code bytes differ.** | -| 15 | `ctest -L integration-gpu -j 8` (as configured) | 99%: 4 failed of 868 — three `*IsActuallyArmedWhenTheEnvironmentPinsItOn*` (the known `-j` flake) + `DirectVulkan.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput`; `--rerun-failed -j 1`: the three flakes pass, Program203 still fails (1 texel at (0,0), actual `(0x357a,0x4b65)` vs golden `(0x3a74,0x4821)`) — **diagnosed below as a configure difference of the build dir, not a regression** | -| 16 | `ctest -L integration-gpu -j 8` with `MOBILEGL_MAGMA_FIX_ITERATIONRP_SUBGROUP_SCRATCH=1 MOBILEGL_MAGMA_DERIVE_NUM_SUBGROUPS=1 MOBILEGL_MAGMA_ITERATIONRP_FIX_BARRIER=1` exported (what CI's integration-gpu job does, per `MG_IntegrationTest/CMakeLists.txt:286-291`), then `--rerun-failed -j 1` | ITEST2_RESULT | - -### Program203 diagnosis (item 15) - -- Passes 3/3 in `~/w7/pipe/build-linux`, fails 3/3 in mine, deterministically, same wrong bits every time. -- `.text` of `libMobileGL.so` **and** of `MobileGLIntegrationTest` are byte-identical between the two trees. -- `ctest -N -V` shows the reference registers the test with `ENVIRONMENT … VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.json; MOBILEGL_MAGMA_FIX_ITERATIONRP_SUBGROUP_SCRATCH=1; MOBILEGL_MAGMA_DERIVE_NUM_SUBGROUPS=1; MOBILEGL_MAGMA_ITERATIONRP_FIX_BARRIER=1`; mine registers only `MOBILEGL_BACKEND_TYPE=DirectVulkan`. -- Cause: `MG_IntegrationTest/CMakeLists.txt:284-298` attaches the three repairs only when `MOBILEGL_ITEST_VK_ICD MATCHES "lvp_icd|lavapipe"`. `~/w7/pipe/build-linux/CMakeCache.txt` has `MOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json`; **`~/w7/p05-value-types/build-linux/CMakeCache.txt` has `MOBILEGL_ITEST_VK_ICD=` (empty)** — the package worktree was configured without the ICD pin. The only ICD on the machine is lavapipe, so the test runs on lavapipe either way, just without the repairs forced on — exactly the "Program 203 misses its golden output" case the CMake comment describes. -- Proof: my binary run by hand with the three variables → `mismatches=0/262656`, PASSED; the reference binary run by hand without them → `mismatches=1/262656`, the identical wrong bits, FAILED. -- I did not reconfigure `build-linux/` (the brief handed it to me as-is); I ran the lane with the variables exported instead (item 16). **Integrator note:** the other two p05 worktrees were presumably configured the same way and will show the same single failure on this test under a plain `ctest -L integration-gpu`; `cmake -DMOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json build-linux` on each fixes the registration. - -## Deviations from the brief - -1. **Deleted the blank separator line after each cut region** (`RenderState.h:371`, `VertexArrayObject.h:71`) in addition to the ranges the brief names, so the forwarding headers do not keep a stray empty line right after the opening `namespace` brace. Whitespace only. -2. **The value header's leading comment is not the brief's two-line PURITY text verbatim**: I spelled the forbidden directories without a trailing slash (`MG_State, MG_Impl, MG_Backend or MG_Remote`) because the brief's own text-purity grep (`grep -n "MG_State/\|…"`) matched the brief's suggested wording; the grep is now empty as the brief requires. Added a short paragraph saying what the header is and why the MG_State headers forward to it. -3. **`MGPipeTypes.h` debt comment / `gen_pipe.py` comments are my wording**, following the brief's content instructions (MG_State half repaid; `DynamicBackendParameters` remains and keeps gate A off `MGPipeTypes.h`; field lists in P1). `gen_pipe.py:165` was wrapped onto two lines to stay under the file's line width. -4. **`ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail` pins two more offsets than the brief lists** (`RenderState` at 0, `PatchVertices` at 1208) and `ValueTypeLayoutsArePinned` also checks `ColorMasks`' extent and the trivially-copyable/standard-layout predicates at runtime. Additions only. -5. **No `scripts/symbol_report.py`** in this tree (B.3 deliverable, not yet landed): the nm comparison was done by hand as the task allows; every byte of difference is attributed (item 14). -6. **`ctest -L integration-gpu` was run twice**: once as configured (item 15, one non-regression failure explained above) and once with CI's three environment variables exported (item 16). No test file, CMake file or build configuration was changed to get there. -7. `scripts/check_include_closure.py --mode both --probe value-header` (the "once p05/include-gate exists" line) was not run: the script is not in this tree. The equivalent manual `-H` probe (items 8-9) is green. - -## Unfinished - -UNFINISHED_TEXT diff --git a/docs/Disaggregated/notes/recovered/wf4/BRIEF-P1.md b/docs/Disaggregated/notes/recovered/wf4/BRIEF-P1.md deleted file mode 100644 index 35ddf373..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/BRIEF-P1.md +++ /dev/null @@ -1,572 +0,0 @@ -# P1 implementation brief — PipeInputs strangler, per-verb fill points, poison generations, MOBILEGL_PIPE_VERIFY comparator and its CI mode - -Tree: `feat/disaggregated @ 087685d1` (worktree `C:/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/MobileGL-disagg`; WSL `~/w7/pipe` is a worktree of the same repo at `087685d1`, branch `feat/disaggregated`, with a configured `build-linux/` — Release, `/usr/sbin/clang++` (clang 22.1.6; there is no `clang++-20` in WSL), ccache, `MOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO`, `MOBILEGL_BUILD_TEST=ON`, `MOBILEGL_BUILD_INTEGRATION_TEST=ON`, `MOBILEGL_BUILD_DISAGGREGATED=OFF`, `MOBILEGL_ITEST_EGL_VENDOR=/usr/share/glvnd/egl_vendor.d/50_mesa.json`, `MOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json`, `MOBILEGL_ITEST_REQUIRE_GPU=OFF`; `build-retrace/` exists; 94 fixture files under `tools/trace_replay/fixtures/`; 28 cores). `~/w7/base` is `dev@81b17c0b` (the performance anchor, untouched). The P0.5 tooling in `scratchpad/wf2/` (`wsl_p05_trees.sh`, `wsl_integrate_p05.sh`, `retrace_gate.py`, `wsl_retrace_baseline.sh`) is the shape every WSL step below copies with `p05`→`p1`. - -Every `file:line` below was re-opened at `087685d1` (the scouts were written at `5635e33f`; the one commit between is CI-only, `.github/workflows/test.yml`, and every scout line number still holds). Corrections to the scouts are marked **[correction]**; deliberate deviations from the design documents are marked **[deviation]** and carry their reason. - -**Scout verification summary.** The pull-site catalogue (`scout-pull-sites.md` §1) was cross-checked mechanically against the live tree (`scratchpad/wf4/xcheck.py`): all 335 `pGLContext` occurrences on 332 lines in 12 files match the catalogue line-for-line and accessor-for-accessor (the script's one "DIFF" for `VulkanRenderer.cpp` is the parser swallowing the §5 sync tables that follow that heading; 158 − 26 = 132 = live). Spot-reads of 40+ sites across both backends confirmed the `enclosing function` / `verb path` columns with one exception, **[correction]** `VkRenderPassManager.cpp:1111` sits inside `VkRenderPassManager::GetOrCreateRenderPass` (the `ConvertTextureInternalFormatToVkEnum` the catalogue names at `:1044` is `MG_Util::`'s, a different function that reads nothing), so all three `VkRenderPassManager.cpp` reads are render-pass creation — reached from draws, clears and blits only. No `pGLContext` use exists in `MG_Backend` outside the 12 catalogued files (`Init.cpp`, `BackendObject.*`, `BackendObjects.h` and every other `.h` are clean); no other `MG_State::` global is reached (`MG_State::pGLContext` is the only one, 335/335). Other corrections: `scout-docs-spec.md` says "~34 `MOBILEGL_ASSERT`" — the live count is **43** (34 in `DirectVulkan.cpp` + 9 in `UniformManager.cpp`); `scout-pull-sites.md` cites `MGPipeHostSpan.h:119-154` — the file is 57 lines, `struct MGHostSpan` is `:28-37`; `scout-test-infra.md` B2/B4 gate the poison on `MOBILEGL_DEBUG`, which does not exist anywhere in the tree (D2 below). The MG_Impl boundary is **83 call statements over 69 distinct table entries**, not "~93 sites" (the 91-line grep counts null-checks and captured-pointer lines; §B.7). `Present` and `SetSwapInterval` are never called through `gBackendFunctionsTable` from `MG_Impl` — EGL goes through `BackendObject` virtuals (`EGLImpl.cpp:178,448`) — and neither backend's present path reads `pGLContext`, so they are not fill points. - ---- - -## A. Goal and acceptance gate - -`docs/Disaggregated/ROADMAP.md:17`, verbatim: - -> **P1** `PipeInputs` 替换与 verify harness | 10–13 | `MG_Backend/MGPipe/PipeInputs.h`(Espryt 32 / Magma 55 访问器);`sed` 293 处 + 58 行非箭头清单逐条转换(显式交付物);逐 verb 类填充点(G5 表,~93 个边界站点);逐 verb 世代 poison;G4 影子比对器 + 第三种 CI 模式;20 处 `SyncPersistentMappedRange` + 6 处 `SyncGpuWrites` 的逐站点归属表 | pull 构建 `nm --defined-only` 不变、`.text` 差异逐行归因(空守卫/三元重写推迟到 P2);40 trace + 全部集成测试在 `MOBILEGL_PIPE_VERIFY=1` 下零分歧;故意损坏一个快照字段能让 verify 变红;故意在 `glGenerateMipmap` 的填充表漏一个字段能在**那条 verb** 上触发 poison Fatal | P0.5 - -Binding context: `ARCHITECTURE.md:325-340` (the `PipeInputs`/`MGB_CTX` sketch), `:342-347` (phase A/B/C table — P1 is phase A plus the phase-B scaffolding: verify harness, poison, fill points), `:348-349` (fill points per verb class; poison is a per-verb generation, `Fatal{UnmigratedPipeInput, "@"}`; gate C greps `pGLContext`, not `pGLContext->`), `:501` (gate 13.2-②: two state models in one address space, `SnapshotFromGLContext()`, field-wise, first differing field + draw serial, third CI mode 5–10× slower, never shipped, survives P13), `:369` (P13 keeps `MOBILEGL_PIPE_VERIFY` with the `MG_State` include it needs), `:553` (`MG_Backend/MGPipe/` is created in P1), `:583` (`MOBILEGL_PIPE_VERIFY` is a **CMake** option, planned P1). `ROADMAP.md:7`: **每个门必须能因它存在的理由变红**; Windows is not a correctness gate. `ROADMAP.md:38`: day 25 is the P1 exit, "verify harness 逐 draw 逐字段证明推送等价于拉取", not a GO/NO-GO. `ROADMAP.md:46`: the first GO/NO-GO item is P1's proof. - -Decoded into checkable statements: - -| # | statement | how it is checked | -|---|---|---| -| G1 | The **pull build** (`MOBILEGL_PIPE_PUSH=OFF`, `MOBILEGL_PIPE_VERIFY=OFF`, Release/INFO, LTO off) has an identical `nm --defined-only` symbol set before and after P1, and a `.text` byte delta of **zero**; if not zero, every differing symbol is attributed to one of the 58 converted non-arrow lines (D9). | `scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 --json` → `added == removed == resized == renamed == 0`, `.text` delta 0 (C.integrator). | -| G2 | Every backend read of frontend state goes through `MGB_CTX` (335 occurrences → 0 `pGLContext` tokens in `MG_Backend/**` except inside the pull arm of the one switch header, which lives in `MG_Pipe/`). | `grep -rc "pGLContext" MobileGL/MG_Backend | grep -v ":0$"` is empty. | -| G3 | In the **verify build** (`MOBILEGL_PIPE_VERIFY=ON` ⇒ `MOBILEGL_PIPE_PUSH=ON`), with `MOBILEGL_PIPE_VERIFY=1` in the environment, the 40-trace retrace (79 desktop cases: 40 × 2 backends minus `iterationrp × DirectGLES`; `create-indirect` waived on device only) and **every** integration entry (the 742-entry `integration-verify` lane) run with zero `Fatal{PipeVerifyDiffer` and zero `Fatal{UnmigratedPipeInput` and the arming line present in every log. | `retrace_gate.py` under `MOBILEGL_PIPE_VERIFY=1` against the verify `.so`; `ctest -L integration-verify --no-tests=error`; `run_trace_case.cmake`'s arming/differ assertions (E). | -| G4 | Negative control A: `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` turns a green verify run red, naming that field and the verb serial. | unit (A), lane (E), CI step (E). | -| G5 | Negative control B: `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit` makes `glGenerateMipmap` abort with `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}` on **that** call, and no other verb is affected (a sibling field on the same verb stays fresh; the preceding draw does not abort). | unit (A), fork/waitpid scenario per backend (E). | -| G6 | Every one of the 20 `SyncPersistentMappedRange` and 6 `SyncGpuWrites` sites has a row in the ownership table (D10) stating the §5.7 reconcile class it must reproduce; the `*IndirectCount` rows record today's behaviour verbatim (open question #15, not fixed in P1). | the table is in this brief; the integrator lands it in `docs/Disaggregated/MEASUREMENTS.md`. | -| G7 | Existing test names unchanged (additions only); `pipe-gates`, `include-graph-check`, `test`, `integration`, `retrace` all green; the new generated file is regenerated by `gen_pipe.py` and diffed by CI. | `ctest -N` name diff (`comm -23` empty); `gh workflow run test.yml --ref feat/disaggregated`. | -| G8 | The gate itself can go red for its reason: the arming assertion fails a lane whose library never armed; `--no-tests=error` fails a lane whose label matched nothing; the two negative controls are always-on CI steps. | E. | - ---- - -## B. Fixed design decisions (every package follows these; nothing here is re-decided inside a package) - -### D1 — Counts, measured at `087685d1` - -| what | value | -|---|---| -| `pGLContext` occurrences / lines in `MG_Backend/**` | **335 / 332** (3 lines hold two: `VulkanRenderer.cpp:9214,10719,10963`) | -| arrow sites `pGLContext->` | **277** occurrences on 274 lines: `DirectGLES.cpp` 91, `Managers.cpp` 15, `MultiDraw.cpp` 5, `Utils.cpp` 2, `BackendObject_DirectVulkan.cpp` 2, `DirectVulkan.cpp` 12, `UniformManager.cpp` 14, `VkClearManager.cpp` 1, `VkRenderPassManager.cpp` 3, `VkTextureManager.cpp` 2, `VulkanRenderer.cpp` 127 (130 occurrences) | -| non-arrow lines | **58**: 43 `MOBILEGL_ASSERT`, 7 null guards, 5 `!= nullptr` outside asserts, 1 `== nullptr`, 1 `.get()`, 1 comment (D9 lists every line) | -| distinct accessors read | **62** (Espryt 32, Magma 56) + one `.get()` | -| `PipeInputs` field set | **63** = the 61 rows of `Coverage.def:30-97` + `GetBoundTransformFeedbackLifetimeId` + `HasOpenTransformFeedbackSpan` (D13). `GetBoundTransformFeedbackName` (`Coverage.def:34`) is read by no backend; it stays as a field so the vendored inventory row `backend_read_inventory.md:594` keeps its mapping (`kMGPipeInventoryUnmapped == 0` is asserted at `PipeCatalogueTest.cpp:213-214`), and is marked dead in the header. | -| MG_Impl fill points | **83** call statements over **69** verbs (every function-pointer member of `GLFunctionsTable`, `BackendObject.h:117-289`); `CompileEnv.cpp:135,137` (`MG_Util`) also calls `GL.GetIntegeri_v` and gets no fill point (the entry reads no frontend state on either backend) | -| `SyncPersistentMappedRange` / `SyncGpuWrites` | 20 / 6, exactly as catalogued (D10) | - -ROADMAP's "293 / 32+55 / ~93" are the vendored-inventory numbers; the integrator corrects them in the docs (C.5). `MEASUREMENTS.md:82`'s "61 个 PipeInputs 字段" becomes 63. - -### D2 — Switches and their spellings - -| switch | kind | default | meaning | -|---|---|---|---| -| `MOBILEGL_PIPE_PUSH` | CMake `option()` + compile definition `-DMOBILEGL_PIPE_PUSH=1` (in `MOBILEGL_COMPILE_DEF`, `CMakeLists.txt:523-525` shape) | OFF | `MGB_CTX` expands to `&gPipeInputs`; the fill points and the filler are compiled; `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp` and `MobileGL/MG_Impl/Pipe/PipeFill.cpp` are appended to `SOURCE_FILES`. OFF is the pull build and must be byte-identical to today. | -| `MOBILEGL_PIPE_VERIFY` | CMake `option()` + `-DMOBILEGL_PIPE_VERIFY=1`; **forces `MOBILEGL_PIPE_PUSH` ON** (`set(MOBILEGL_PIPE_PUSH ON)` as a normal variable in the configure that sees VERIFY, with a `message(STATUS)`) | OFF | compiles the second snapshot, the entry comparator, the compare-at-read hook and the two negative-control knobs. Never shipped. | -| `MOBILEGL_PIPE_POISON` | derived preprocessor macro, defined **once**, in `MobileGL/MG_Backend/MGPipe/PipeInputs.h`: `#if MOBILEGL_PIPE_PUSH && (MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG || MOBILEGL_BUILD_DISAGGREGATED || MOBILEGL_PIPE_VERIFY)` → `#define MOBILEGL_PIPE_POISON 1` else `0` | — | the per-verb generation stamps and the read-side `Fatal{UnmigratedPipeInput}` check. **[correction]** the docs' `MOBILEGL_DEBUG` does not exist; the repo's debug gate is `MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG` (`Defines.h:104-115`). The verify CI build is Release/INFO with `MOBILEGL_BUILD_DISAGGREGATED=OFF`, so the third arm is what arms the poison there without dragging `MG_Remote` in. | -| runtime `MOBILEGL_PIPE_PUSH` (`Features.PipePush`, `Config.h:326`, `ConfigLoader.cpp:245`) | Uint64 bitmask | 0 | **not consumed in P1.** The P1 filler fills every field in a verb's mask regardless of the bitmap; P2 makes the filler yield per bit to the tracker. Bit 63 stays reserved for the CSO negative control. Same name for the macro and the env var is deliberate (`ARCHITECTURE.md:334,367`): the macro selects the arm, the bitmap selects subsystems inside it. | -| runtime `MOBILEGL_PIPE_VERIFY` (`Features.PipeVerify`, `Config.h:332`, `ConfigLoader.cpp:246`) | Bool | 0 | arms the comparator at run time in a verify build. Ignored (with one `MGLOG_W_ONCE`) in a build where `MOBILEGL_PIPE_VERIFY` is not compiled in — that warning is what the arming assertion turns into red. | -| runtime `MOBILEGL_PIPE_VERIFY_FATAL` | Bool, new `Features.PipeVerifyFatal`, parsed `QueryEnvQuirkOverride(...) != ForceOff` (tri-state, like `PipeLegacyMemos`, `ConfigLoader.cpp:251-252`) | 1 | first divergence aborts (`MGLOG_F` + `std::abort()`); `0` logs and counts, for triage and for the lane that must survive to read its own log. | -| runtime `MOBILEGL_PIPE_VERIFY_CORRUPT` | String, new `Features.PipeVerifyCorrupt` (`QueryEnvVariable`) | "" | `` from `kMGPipeInputFieldNames[]`; perturbs that field in the **snapshot** arm before the entry compare. Unknown name → `Fatal{PipeVerifyBadKnob}`. | -| runtime `MOBILEGL_PIPE_POISON_OMIT` | String, new `Features.PipePoisonOmit` | "" | `:` (`kMGPipeVerbNames[]` × `kMGPipeInputFieldNames[]`); the filler skips the **stamp** (not the value) of that field for that verb. Unknown name → `Fatal{PipeVerifyBadKnob}`. | - -The three new `Features` members and their three parser lines are wrapped in `#if MOBILEGL_PIPE_PUSH` in `Config.h` and `ConfigLoader.cpp` so the pull build's `Features` object is unchanged in size (G1). `ConfigLoader.cpp:242-244`: no allow-list edit is needed for `MOBILEGL_*` names. - -### D3 — `MGB_CTX` and the switch header (exact text; A creates it, B and C include it) - -`MobileGL/MG_Pipe/PipeInputsSwitch.h` — lives under `MG_Pipe/`, never under `MG_Backend/`, so the pull arm's `pGLContext` spelling is outside what purity gate C greps (`ARCHITECTURE.md:500,349`): - -```cpp -// MobileGL - MobileGL/MG_Pipe/PipeInputsSwitch.h -// (banner lines 2-7 as in MGPipe.h:2-7) -#pragma once -#ifndef MOBILEGL_MG_PIPE_INPUTS_SWITCH_H // belt and braces: reachable as and <..> (CMakeLists.txt:531,535) -#define MOBILEGL_MG_PIPE_INPUTS_SWITCH_H -// The strangler switch (ARCHITECTURE.md 9.2). Every backend read of frontend state is spelled -// MGB_CTX->Accessor(...). Pull arm: the live GLContext, so the pull build is the tree before P1 -// token for token. Push arm: the PipeInputs block the frontend fills at every verb boundary. -// The pull arm is the ONLY place under MobileGL/ outside MG_State and MG_Impl that may spell -// pGLContext; purity gate C greps MG_Backend/ for that token. -#if MOBILEGL_PIPE_PUSH -#include -#define MGB_CTX (&::MobileGL::MG_Pipe::gPipeInputs) -#define MGB_CTX_LIVE (::MobileGL::MG_Pipe::gPipeInputs.IsLive()) -#define MGB_CTX_IDENTITY (::MobileGL::MG_Pipe::gPipeInputs.ContextIdentity()) -#else -#include -#define MGB_CTX (::MobileGL::MG_State::pGLContext) -#define MGB_CTX_LIVE (::MobileGL::MG_State::pGLContext != nullptr) -#define MGB_CTX_IDENTITY (static_cast(::MobileGL::MG_State::pGLContext.get())) -#endif -#endif -``` - -`MG_State::pGLContext` is `extern UniquePtr& pGLContext;` (`Core.h:596`), so `MGB_CTX->X()` in the pull arm is exactly today's `MG_State::pGLContext->X()`, and `MGB_CTX_LIVE` is `UniquePtr::operator bool`'s definition spelled out. Backend TUs include `` immediately after their `MG_State/GLState/Core.h` include (`DirectGLES.cpp:18`, `MultiDraw.cpp:11`, `Utils.cpp:19`, `BackendObject_DirectVulkan.cpp:14`, `DirectVulkan.cpp:12`, `UniformManager.cpp:12`, `VkClearManager.cpp:15`, `VkTextureManager.cpp:13`, `VulkanRenderer.cpp:16`; `Managers.cpp` gets it after its `Managers.h` include, `VkRenderPassManager.cpp` after its own include block — neither includes `Core.h` directly). - -### D4 — `PipeInputs`: one struct, three field classes, types identical to today's reads - -`MobileGL/MG_Backend/MGPipe/PipeInputs.h`, `namespace MobileGL::MG_Pipe`, `struct PipeInputs` + `inline PipeInputs gPipeInputs{};` (the single global the docs name, `ARCHITECTURE.md:331`; inline variable, no `.cpp` needed for the definition). Includes: `` (field ids, verb enum, poison helpers), `` (the frontend types the accessors return — allowed: P13 keeps the `MG_State` include for the verify arm, `ARCHITECTURE.md:369`; the header spells **no** `pGLContext` token). Every accessor has the **same name, parameters and return type** as its `GLContext` counterpart (`Core.h:66-506`), so the `sed` is type-neutral (phase A rule, `ARCHITECTURE.md:328`). Three storage classes: - -- **V (value)** — copied out of `GLContext` at fill time by calling the same accessor (`m_x = ctx->GetX(...)`; no derivation logic is re-implemented in `PipeInputs`, which is what keeps the copy semantically identical by construction). Accessor returns the copy (by value or `const&`, whichever `Core.h` uses). -- **O (object reference)** — a `SharedPtr` copy, or a raw pointer to the live `GLContext`-owned slot/array for the accessors that return a non-const reference into the context. Same return type as today; identity is what phase C turns into a handle. -- **F (forwarded)** — argument-keyed lookups and reverse-channel calls. Declared in `PipeInputs.h`, **defined out of line in `MobileGL/MG_Impl/Pipe/PipeFill.cpp`** (the client side, where `pGLContext` may be spelled), forwarding to the live context. Sticky (D6). - -| field (id from `kMGPipeInputFieldNames`) | class | storage in `PipeInputs` | accessor signature (= `Core.h`) | fill | -|---|---|---|---|---| -| GetActiveTextureUnit | V | `Int m_activeTextureUnit` | `Int GetActiveTextureUnit() const` | `ctx->GetActiveTextureUnit()` | -| GetBlendColor | V | `FloatVec4 m_blendColor` | `const FloatVec4& GetBlendColor() const` | | -| GetBlendEquationIndexed | V | `BlendEquation m_blendEquation[kMGMaxDrawBuffers][2]` | `void GetBlendEquationIndexed(Uint, BlendEquation&, BlendEquation&) const` | loop i<8 | -| GetBlendFuncIndexed | V | `BlendFactor m_blendFunc[kMGMaxDrawBuffers][4]` | `void GetBlendFuncIndexed(Uint, BlendFactor&×4) const` | loop i<8 | -| GetBoundTransformFeedbackName | V | `Uint m_boundTransformFeedbackName` | `Uint GetBoundTransformFeedbackName() const` | **dead field**: filled, never read (D1) | -| GetBoundVertexArray | O | `SharedPtr m_boundVertexArray` | `const SharedPtr& GetBoundVertexArray()` | copy | -| GetBufferBindingSlot | O | `BindingSlot* m_bufferBindingSlot[BufferTargetCount]` | `BindingSlot& GetBufferBindingSlot(BufferTarget)` | `&ctx->GetBufferBindingSlot(t)` for every `t` in `GlobalBufferTargets` (`BufferState.h:15`); others null; accessor asserts non-null | -| GetBufferBindingPoint | O | `BindingSlotRange1D* m_bufferBindingPointBase[BufferTargetCount]` | `BindingSlotRange1D& GetBufferBindingPoint(BufferTarget, Uint)` | `&ctx->GetBufferBindingPoint(t, 0)` for `t` in `BufferBindPointTargets` (`BufferState.h:20`); storage is `Array, N>` (`BufferState.h:73`) so `base[index]` is the live slot | -| GetBufferBindingPointCount | F | — | `SizeT GetBufferBindingPointCount(BufferTarget) const` | forward (constexpr table) | -| GetTouchedBufferBindingPointCount | V | `SizeT m_touchedBindingPointCount[BufferTargetCount]` | `SizeT GetTouchedBufferBindingPointCount(BufferTarget) const` | loop over `BufferBindPointTargets` | -| GetClampReadColor | V | `GLenum` | `GLenum GetClampReadColor() const` | | -| GetClearColor / GetClearDepth / GetClearStencil | V | `FloatVec4` / `Float` / `Uint32` | as `Core.h:272-276` | | -| GetColorMaskIndexed | V | `BoolVec4 m_colorMask[kMGMaxDrawBuffers]` | `BoolVec4 GetColorMaskIndexed(Uint) const` | loop | -| GetCullFaceMode | V | `CullFaceMode` | | | -| GetCurrentVertexAttribute | V | `CurrentVertexAttributeValue m_currentVertexAttribute[VertexArrayObject::MAX_VERTEX_ATTRIBS]` (32, `VertexArrayObject.h:24`) | `const CurrentVertexAttributeValue& GetCurrentVertexAttribute(Uint) const` | loop | -| GetDepthFunc / GetDepthMask | V | `DepthTestFunc` / `Bool` | | | -| GetDepthRangeIndexed | V | `FloatVec2 m_depthRange[RenderStateParameters::MAX_VIEWPORTS]` | `const FloatVec2& GetDepthRangeIndexed(Uint) const` | loop 16 | -| GetFramebufferBindingSlot | O | `BindingSlot* m_framebufferBindingSlot[FramebufferTargetCount]` | `BindingSlot& GetFramebufferBindingSlot(FramebufferTarget)` | both targets | -| GetImageTextureBinding | O | `ImageTextureBinding* m_imageTextureBindingBase` | `ImageTextureBinding& GetImageTextureBinding(Int)` (+ const overload) | `&ctx->GetImageTextureBinding(0)`; storage `Array` (`TextureState.h:129`) | -| GetLineWidth / GetLogicOp / GetMaxTouchedTextureUnit / GetMinSampleShadingValue | V | `Float` / `LogicOperation` / `Int` / `Float` | | | -| GetPatchDefaultInnerLevel / GetPatchDefaultOuterLevel / GetPatchVertices | V | `FloatVec2` / `FloatVec4` / `Uint` | `const FloatVec2&` / `const FloatVec4&` / `Uint` | | -| GetPipelineStateVersion / GetRenderStateParametersVersion | V | `Uint` / `Uint` | | | -| GetPixelStoreParameters | V | `PixelStoreParameters m_pixelStore[2]` (`[0]`=pack, `[1]`=unpack) | `PixelStoreParameters GetPixelStoreParameters(Bool isUnpack) const` | both | -| GetPolygonModeFront / GetPolygonOffsetFactor / GetPolygonOffsetUnits / GetPrimitiveRestartIndex / GetProvokingVertexMode | V | `GLenum` / `Float` / `Float` / `Uint32` / `ProvokingVertexMode` | | | -| GetProgramForDispatch / GetProgramForDraw / GetTransformFeedbackProgram | O | `SharedPtr` ×3 | `const SharedPtr& ...()` | copy (`GetProgramForDraw()` is called once per fill; it has a guarded static, `DirectGLES.cpp:2965-2968`) | -| GetProgramObject | F | — | `const SharedPtr& GetProgramObject(Uint)` | forward | -| GetRenderStateParameters | V | `RenderStateParameters m_renderState` (1168 B) | `const RenderStateParameters& GetRenderStateParameters() const` | memcpy-by-assignment | -| GetSamplingResolutionGeneration / GetTextureBindGeneration / GetTextureContextId | V | `Uint64` ×3 | | | -| GetScissorBox | V | `IntVec4` | `const IntVec4& GetScissorBox() const` | | -| GetStencilState | V | `StencilFaceState m_stencil[2]` | `const StencilFaceState& GetStencilState(StencilFace) const` | both faces | -| GetTextureObject | F | — | `const SharedPtr& GetTextureObject(Uint)` | forward | -| GetTextureUnitObject | O | `TextureUnit* m_textureUnitBase` | `TextureUnit& GetTextureUnitObject(Int)` | `&ctx->GetTextureUnitObject(0)`; storage `Array` (`TextureState.h:41,128`) | -| GetTransformFeedbackCapturedVertices / GetTransformFeedbackGeneration / GetTransformFeedbackPausedPrimitiveCounter / GetBoundTransformFeedbackLifetimeId | V | `Uint64` ×4 | as `Core.h:334,349,357,441` | | -| GetViewport | V | `IntVec4` | `IntVec4 GetViewport() const` | | -| GetViewportIndexed | V | `FloatVec4 m_viewport[MAX_VIEWPORTS]` | `const FloatVec4& GetViewportIndexed(Uint) const` | loop 16 | -| IsCapabilityEnabled | V | `Bool m_capability[CapabilityInputCount]` (37 entries, `MGPipeValueTypes.h:168-204`) | `Bool IsCapabilityEnabled(CapabilityInput) const` | loop over all caps (`FramebufferSrgb` copies today's constant `false`, `MEASUREMENTS.md:84` — no change of value) | -| IsCapabilityEnabledIndexed | V | `Bool m_blendIndexed[kMGMaxDrawBuffers]`, `Bool m_scissorIndexed[MAX_VIEWPORTS]` | `Bool IsCapabilityEnabledIndexed(CapabilityInput, Uint) const` | `Blend` ×8, `ScissorTest` ×16; any other cap → `Fatal{UnmigratedPipeInput}` with the cap name (no backend asks; `VulkanRenderer.cpp:5437` asks `Blend`) | -| IsTransformFeedbackActive / IsTransformFeedbackPaused | V | `Bool` ×2 | | | -| HasOpenTransformFeedbackSpan | F | — | `Bool HasOpenTransformFeedbackSpan(Uint64) const` | forward | -| InvalidateCompileEnv / ValidateProgramName / RecordError | F | — | `void InvalidateCompileEnv()` / `Bool ValidateProgramName(Uint) const` / `void RecordError(ErrorCode, UniquePtr)` | forward (`RecordError` forwards even when no context is live? no: `IsLive()` false → drop with `MGLOG_E_ONCE`; today's guarded sites (`Managers.cpp:8750`) never reach it without a context) | - -Non-field members: `const void* m_contextIdentity` (the live `GLContext` address at fill; `ContextIdentity()` serves `MGB_CTX_IDENTITY`), `Bool m_live` (`IsLive()`), `MGPipeVerb m_currentVerb`, and under `MOBILEGL_PIPE_POISON` an `MGPipeFilledState m_filled` (`PipeFilled.inc:293-296`, `CurrentVerbSerial` + `FilledGen[63]`). Estimated size ≈ 3.5 KB (well under the docs' ~20 KB). `static_assert(sizeof(PipeInputs) < 20 * 1024)`. - -Every accessor body is: `MGP_INPUT_CHECK(MGPipeInputField::X);` (poison, D6) → `MGP_INPUT_VERIFY_READ(MGPipeInputField::X, idx0, idx1);` (compare-at-read, D8) → return the storage. Both macros expand to nothing when their switch is off, so a plain `MOBILEGL_PIPE_PUSH` build's accessor is a load. - -Known gap, closed in P2 (Espryt 0b deletes `g_fbSlotCache`, `ROADMAP.md:18`): the five reads through `GetFramebufferBindingSlotFast` (`DirectGLES.cpp:1611,1905,2743,2858,2933`) bypass the accessor once the cache is warm, so they are not poison-checked; the entry compare still covers `GetFramebufferBindingSlot`. - -### D5 — Files created, and where the `pGLContext` spelling is allowed - -| path | owner | contents | may spell `pGLContext`? | -|---|---|---|---| -| `MobileGL/MG_Pipe/PipeInputsSwitch.h` | A | D3 | only in the `#else` arm | -| `MobileGL/MG_Pipe/FillPoints.def` | A | D7 source of truth | no | -| `MobileGL/MG_Pipe/generated/PipeFillPoints.inc` | A (generated) | verb enum, class tables, masks | no | -| `MobileGL/MG_Backend/MGPipe/PipeInputs.h` | A | D4 struct, accessors, `gPipeInputs`, `MOBILEGL_PIPE_POISON`, the two accessor macros | **no** | -| `MobileGL/MG_Backend/MGPipe/PipeInputs.cpp` | A | `MGPipeInputPoisonFatal` wrapper with verb name, per-field equality (`MGPipeInputsFieldEqual`), corruption injector | **no** | -| `MobileGL/MG_Impl/Pipe/PipeFill.h` | A | `MGP_FILL(Verb)` macro; declarations of `MGPipeFillForVerb`, `SnapshotFromGLContext` | no (macro expands to nothing under pull) | -| `MobileGL/MG_Impl/Pipe/PipeFill.cpp` | A | the filler (non-verify branch), `SnapshotFromGLContext` (verify branch), the F-class forwarders, `IsLive`, compare-at-read hook, entry compare, knob parsing | yes (client side) | - -**[deviation]** `ARCHITECTURE.md:553` lists `MGPipeImpl_DirectGLES.cpp` / `MGPipeImpl_DirectVulkan.cpp` as `[P1+]`. P1 creates neither: nothing backend-specific exists in `PipeInputs` until a backend installs an `MGPipeContext` table (P5). The directory `MG_Backend/MGPipe/` is created with `PipeInputs.h/.cpp`; the integrator records this in the docs. - -### D6 — Poison: per-verb generation, seven sticky fields, the Fatal - -- `MGPipeFillForVerb(MGPipeVerb v)` (`PipeFill.cpp`) does, in order: `++m_filled.CurrentVerbSerial` (starts at 1 so `FilledGen == 0` means never filled); `m_currentVerb = v`; if `pGLContext == nullptr` → `m_live = false`, return; else `m_live = true`, `m_contextIdentity = ctx`, then for every field in `kMGPipeClassFieldMask[kMGPipeVerbClass[v]]`: copy per D4 and `m_filled.FilledGen[f] = CurrentVerbSerial` — **unless** `(v, f)` equals the parsed `MOBILEGL_PIPE_POISON_OMIT` pair, in which case the value is still copied and only the stamp is skipped (an omission indistinguishable from a forgotten row). Cost is irrelevant in P1 (verify build) and is replaced by the tracker in P2. -- `MGP_INPUT_CHECK(f)` under `MOBILEGL_PIPE_POISON`: `if (!MGPipeInputFieldIsFresh(m_filled, f)) MGPipeInputPoisonFatalForVerb(f, m_currentVerb);` → `MGPipeInputPoisonFatal(f, kMGPipeVerbNames[v])` (`PipeFilled.inc:298-302`: `MGLOG_F("MGPipe: Fatal{UnmigratedPipeInput, \"%s@%s\"}")` + `std::abort()`; before the first verb the name is `""`). `MGLOG_F` is live at every log level (`Log.h:87-92`) and this is not `MOBILEGL_ASSERT` on purpose (inert in INFO builds, `Defines.h:113-115`). -- **Sticky = exactly the seven F-class fields**: `RecordError`, `InvalidateCompileEnv`, `ValidateProgramName`, `GetProgramObject`, `GetTextureObject`, `HasOpenTransformFeedbackSpan`, `GetBufferBindingPointCount`. Argument (goes into `Coverage.def` as the reason string): each takes an argument that is not verb state — a GL name, a lifetime id, a target — i.e. it is a lookup or a reverse-channel write, not a state read; there is no value the filler could copy and no verb whose fill could make it stale; phase C replaces them with handle tables/callbacks. They are stamped once (`FilledGen = 1`) by the first fill that sees a live context and pass `MGPipeInputFieldIsFresh` through the `Sticky → FilledGen != 0` branch (`PipeFilled.inc:304-307`). **None of the version/generation accessors is sticky** (`GetTextureContextId`, `GetTextureBindGeneration`, `GetSamplingResolutionGeneration`, `GetTransformFeedbackGeneration`, `GetPipelineStateVersion`, `GetRenderStateParametersVersion` change under verbs and are precisely what the poison must protect; scout R5). -- `kMGPipeInputFieldSticky[]` stops being all-`false`: `Coverage.def` gains `MGP_COVERAGE_STICKY_LIST(X)` with seven `X(Accessor, "reason")` rows; `gen_filled` emits `true` for those and `sys.exit`s on a name that is not an accessor. -- `IsCapabilityEnabledIndexed` with a cap other than `Blend`/`ScissorTest`, and `GetBufferBindingSlot`/`GetBufferBindingPoint` with a target the fill left null, are also `MGPipeInputPoisonFatal` (the message names the field and the verb; the cap/target goes in a preceding `MGLOG_E`). - -### D7 — Fill points: 69 verbs, 9 classes, one `.def`, one generated `.inc`, one macro - -`MobileGL/MG_Pipe/FillPoints.def` (hand-maintained, A): - -``` -// X(Verb, Class) — one row per function-pointer member of MG_Backend::GLFunctionsTable (BackendObject.h:117-289), -// in declaration order. gen_pipe.py parses that struct and refuses a row set that is not exactly its member set. -#define MGP_FILL_VERB_LIST(X) \ - X(DrawArrays, kDraw) X(DrawElements, kDraw) ... X(GetGpuTimestampNs, kQuery) -// (must be followed by an empty line: gen_pipe.py's block regexes end at a blank line) - -// X(Class) — the nine verb classes (ARCHITECTURE.md:153 names eight; kProgramOp is split out because -// ShaderStorageBlockBinding is the one non-draw verb that syncs Espryt's render state and textures). -#define MGP_FILL_CLASS_LIST(X) \ - X(kDraw) X(kDispatch) X(kClear) X(kBlitOrCopy) X(kTextureOp) X(kReadback) X(kXfbSpan) X(kProgramOp) X(kQuery) - -// X(Class, Field) — the may-read table. A field named here is filled and stamped at every verb of the class; -// a read of a field NOT named here is Fatal{UnmigratedPipeInput, "Field@Verb"} in a poison build. -#define MGP_FILL_FIELD_LIST(X) \ - X(kDraw, GetBoundVertexArray) ... -``` - -Verb → class (69 rows; the count and the membership are the contract, verified by a `gen_pipe.py` parse of `struct GLFunctionsTable`): - -| class | verbs | -|---|---| -| `kDraw` (20) | DrawArrays, DrawElements, DrawElementsBaseVertex, MultiDrawArrays, MultiDrawElements, MultiDrawElementsBaseVertex, MultiDrawElementsIndirect, MultiDrawArraysIndirect, MultiDrawElementsIndirectCount, MultiDrawArraysIndirectCount, DrawRangeElementsBaseVertex, DrawRangeElements, DrawElementsInstancedBaseVertexBaseInstance, DrawElementsInstancedBaseVertex, DrawElementsInstancedBaseInstance, DrawElementsInstanced, DrawElementsIndirect, DrawArraysInstancedBaseInstance, DrawArraysInstanced, DrawArraysIndirect | -| `kDispatch` (2) | DispatchCompute, DispatchComputeIndirect | -| `kClear` (9) | Clear, ClearBufferfi, ClearBufferfv, ClearBufferiv, ClearBufferuiv, ClearNamedFramebufferfv, ClearNamedFramebufferfi, ClearNamedFramebufferiv, ClearNamedFramebufferuiv | -| `kBlitOrCopy` (5) | BlitFramebuffer, BlitNamedFramebuffer, CopyTexImage2D, CopyTexSubImage2D, CopyImageSubData | -| `kTextureOp` (2) | GenerateMipmap, BindImageTexture | -| `kReadback` (3) | ReadPixels, GetTexImage, GetTextureImage | -| `kXfbSpan` (6) | BeginTransformFeedback, EndTransformFeedback, PauseTransformFeedback, ResumeTransformFeedback, BindTransformFeedback, DeleteTransformFeedback | -| `kProgramOp` (1) | ShaderStorageBlockBinding | -| `kQuery` (21) | BeginOcclusionQuery, EndOcclusionQuery, BeginTimeElapsedQuery, EndTimeElapsedQuery, QueryCounterTimestamp, BeginXfbPrimitivesQuery, EndXfbPrimitivesQuery, IsQueryResultAvailable, GetQueryResult64, DeleteBackendQuery, IsTimerQuerySupported, GetGpuTimestampNs, FenceSync, ClientWaitSync, WaitSync, GetSyncStatus, DeleteSync, MemoryBarrier, MemoryBarrierByRegion, PatchParameteri, GetIntegeri_v | - -Class → may-read fields (from the verified reachability, `scout-pull-sites.md` §4.2, union over both backends; the seven sticky fields are implicit everywhere and are not listed). **The verify lane is the oracle: a `Fatal{UnmigratedPipeInput}` found there is fixed by adding the row to this table, never by marking the field sticky.** - -| class | fields | -|---|---| -| `kDraw` | GetBoundVertexArray, GetProgramForDraw, GetBufferBindingSlot, GetBufferBindingPoint, GetTouchedBufferBindingPointCount, GetTextureUnitObject, GetTextureContextId, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetSamplingResolutionGeneration, GetImageTextureBinding, GetCurrentVertexAttribute, GetRenderStateParameters, GetRenderStateParametersVersion, GetPipelineStateVersion, GetViewport, GetViewportIndexed, GetDepthRangeIndexed, GetScissorBox, IsCapabilityEnabled, IsCapabilityEnabledIndexed, GetBlendColor, GetBlendFuncIndexed, GetBlendEquationIndexed, GetColorMaskIndexed, GetLogicOp, GetDepthFunc, GetDepthMask, GetStencilState, GetCullFaceMode, GetPolygonModeFront, GetPolygonOffsetFactor, GetPolygonOffsetUnits, GetLineWidth, GetMinSampleShadingValue, GetProvokingVertexMode, GetPatchVertices, GetPatchDefaultOuterLevel, GetPatchDefaultInnerLevel, GetPrimitiveRestartIndex, GetFramebufferBindingSlot, IsTransformFeedbackActive, IsTransformFeedbackPaused, GetTransformFeedbackProgram, GetTransformFeedbackGeneration, GetBoundTransformFeedbackLifetimeId, GetTransformFeedbackCapturedVertices | -| `kDispatch` | GetProgramForDispatch, GetBufferBindingSlot, GetBufferBindingPoint, GetTouchedBufferBindingPointCount, GetTextureUnitObject, GetTextureContextId, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetSamplingResolutionGeneration, GetImageTextureBinding, GetFramebufferBindingSlot, GetPatchVertices, GetPatchDefaultOuterLevel, GetPatchDefaultInnerLevel (Espryt `SyncCurrentProgram` → `AttachPassthroughTessControlStage`, `Managers.cpp:7193-7204`) | -| `kClear` | GetRenderStateParameters, GetRenderStateParametersVersion, GetViewport, IsCapabilityEnabled, GetFramebufferBindingSlot, GetClearColor, GetClearDepth, GetClearStencil, GetScissorBox, GetColorMaskIndexed, GetDepthMask, GetStencilState, GetTextureUnitObject, GetTextureContextId, GetSamplingResolutionGeneration, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetImageTextureBinding | -| `kBlitOrCopy` | GetFramebufferBindingSlot, IsCapabilityEnabled, GetScissorBox, IsTransformFeedbackActive, IsTransformFeedbackPaused, GetRenderStateParameters, GetRenderStateParametersVersion, GetViewport, GetActiveTextureUnit, GetTextureUnitObject, GetTextureContextId, GetSamplingResolutionGeneration, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetImageTextureBinding, GetColorMaskIndexed, GetDepthMask, GetStencilState | -| `kTextureOp` | GetActiveTextureUnit, GetTextureUnitObject, GetImageTextureBinding, GetTextureContextId, GetSamplingResolutionGeneration, GetTextureBindGeneration, GetMaxTouchedTextureUnit | -| `kReadback` | GetPixelStoreParameters, GetBufferBindingSlot, GetFramebufferBindingSlot, GetActiveTextureUnit, GetTextureUnitObject, GetClampReadColor, IsCapabilityEnabled, GetRenderStateParameters, GetRenderStateParametersVersion, GetViewport, GetTextureContextId, GetSamplingResolutionGeneration, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetImageTextureBinding | -| `kXfbSpan` | GetTransformFeedbackProgram, GetBufferBindingPoint, GetTouchedBufferBindingPointCount, GetTransformFeedbackCapturedVertices, IsTransformFeedbackActive, IsTransformFeedbackPaused, GetTransformFeedbackGeneration, GetBoundTransformFeedbackLifetimeId | -| `kProgramOp` | GetRenderStateParameters, GetRenderStateParametersVersion, GetViewport, IsCapabilityEnabled, GetFramebufferBindingSlot, GetTextureUnitObject, GetTextureContextId, GetSamplingResolutionGeneration, GetTextureBindGeneration, GetMaxTouchedTextureUnit, GetImageTextureBinding | -| `kQuery` | GetTransformFeedbackPausedPrimitiveCounter (`DirectVulkan.cpp:1237,1284`) — every other verb in the class reads nothing; the fill is a serial bump | - -`gen_pipe.py` G5b (`gen_fill_points`) emits `generated/PipeFillPoints.inc`: `enum class MGPipeVerb : Uint8 { DrawArrays, ..., kVerbCount }` (69), `kMGPipeVerbNames[]`, `enum class MGPipeVerbClass : Uint8` (9), `kMGPipeVerbClassNames[]`, `kMGPipeVerbClass[kMGPipeVerbCount]`, `struct MGPipeFieldMask { Uint64 Words[2]; }` + `kMGPipeClassFieldMask[9]` with the seven sticky fields OR'ed in, `MGPipeFieldMaskHas(mask, field)`, and `static_assert(kMGPipeVerbCount == 69)`. It is included from `MGPipe.h` after `PipeFilled.inc` (`MGPipe.h:86`) — the banner's "included from MG_Pipe/MGPipe.h inside namespace MobileGL::MG_Pipe" stays true. - -`MobileGL/MG_Impl/Pipe/PipeFill.h`: - -```cpp -#pragma once -#if MOBILEGL_PIPE_PUSH -#include -namespace MobileGL::MG_Pipe { - void MGPipeFillForVerb(MGPipeVerb verb); // PipeFill.cpp -} -#define MGP_FILL(Verb) ::MobileGL::MG_Pipe::MGPipeFillForVerb(::MobileGL::MG_Pipe::MGPipeVerb::Verb) -#else -#define MGP_FILL(Verb) ((void)0) -#endif -``` - -Placement rule (A applies it at the 83 statements of §B.7): `MGP_FILL(X);` is the statement **immediately before** the call through the table, after every early return the call is behind (the `ConditionalRenderDiscardsCommand()` check in the `*_Backend` wrappers, `GL_Drawing.cpp:529`; the `if (!f) { RecordError; return; }` null checks; the `if (const auto f = table.GL.X) {` guards). A verb whose table entry is null on this backend never bumps. For a call inside a loop (`GL_Query.cpp:915`, `GL_Sync.cpp:229`) the fill is inside the loop body before the call. In the pull build the macro is `((void)0)` (G1). - -### D8 — The comparator: what it compares, where it runs, what it prints - -Two mechanisms, both compiled only under `MOBILEGL_PIPE_VERIFY` and active only when `Features.PipeVerify` is set: - -1. **Entry compare, once per verb** ("每 draw 比一次"): at the end of `MGPipeFillForVerb`, `SnapshotFromGLContext(gPipeSnapshot, mask)` fills a second, file-static `PipeInputs` from the live context (the "verify branch" that survives P13), applies `MOBILEGL_PIPE_VERIFY_CORRUPT` to **the snapshot** (perturb one field: flip a bool, `+1` a scalar, `^0x5A` the first byte of a struct, swap a pointer for `nullptr`), then `MGPipeVerifyInputs(gPipeInputs, gPipeSnapshot, mask, &field)` compares every field in the mask with `MGPipeInputsFieldEqual(field, a, b)` (`PipeInputs.cpp`: V via G4's `MGPipeFieldEqual` — bitwise floats, field-wise structs; O by identity). **Honest statement of what this proves in P1**: both arms come from the same context at the same instant, so it is tautological except under the CORRUPT knob (negative control A) — its oracle role starts in P2 when the tracker fills the first arm. It is nevertheless the mechanism the gate names, and P1 lands it complete. -2. **Compare-at-read, on every accessor**: `MGP_INPUT_VERIFY_READ(f, i0, i1)` calls `MGPipeVerifyReadHook(f, i0, i1)` (`PipeFill.cpp`), which re-reads the same accessor with the same indices from the live context and compares against the stored value. This is the arm that is **real in P1**: it catches any field whose value differs between the verb boundary (what push reads) and the moment of the read (what pull reads) — a frontend counter bumped by the backend mid-verb, a texture re-mint bumping `SamplingResolutionGeneration` between `TrySetupDrawFastPath` and `SetupDraw` (`VulkanRenderer.cpp:6304` vs `:6999`), an error recorded by a helper. Zero divergence here is the semantic statement "push equals pull for every value any backend read in the 40 traces and 808 integration entries". - -Reporting: `MGLOG_F("MGPipe: Fatal{PipeVerifyDiffer, \"%s@%s\", verb=%llu, where=%s}", field, verbName, serial, "entry"|"read")` then `std::abort()` unless `PipeVerifyFatal` is off (then a relaxed-atomic counter, summarised at teardown with `MGLOG_E`). Arming, once, at the first fill under verify: `MGLOG_I("MGPipe: verify armed - %u fields, %u verbs, fatal=%d")` (an `MGLOG_I` with a stated reason, per `PipeStats.cpp:224-231`; the lanes grep for `MGPipe: verify armed`). Knob acknowledgements: `MGLOG_I("MGPipe: verify corruption armed - %s")`, `MGLOG_I("MGPipe: poison omission armed - %s@%s")`. Never `MGLOG_D` (compiled out at INFO, `Log.h:59-64`); never stdio (`test.yml:862-869`). - -G4 growth (A): -- `PipeFields.def` gains `MGP_FIELDS_RenderStateParameters` (all 67 members of `MGPipeValueTypes.h` `struct RenderStateParameters`, in declaration order, `Viewports` … `ScissorBoxes`), `MGP_FIELDS_PixelStoreParameters` (8), `MGP_FIELDS_PerBufferBlendState` (7), `MGP_FIELDS_StencilFaceState` (7), `MGP_FIELDS_DynamicBackendParameters` (every member of `BackendObject.h:316-…`, `Pad`-named members excluded), `MGP_FIELDS_MGHostSpan` (`Ptr`, `Seg`, `Size`, `Offset`; `Pad0` excluded), all appended to `MGP_VERIFY_PAYLOAD_LIST` → `kMGPipeVerifiedPayloadCount` 63 → **69**. `MEMCMP_FALLBACK_TYPES` (`gen_pipe.py:167-172`) becomes empty and the fallback branch in `gen_verify` becomes `static_assert(sizeof(T) == 0, "no field list in PipeFields.def")` — a future memcmp fallback is a compile error, not a padding false positive. -- Two overloads added to `gen_verify`'s emitted `MGPipeFieldEqual`, declared **before** the generic template: `template Bool MGPipeFieldEqual(const Array&, const Array&)` (element-wise; `Array` is `std::array`, `Types.h:57`), and `template Bool MGPipeFieldEqual(const VecBase& a, const VecBase& b)` (element-wise over `data`, so `FloatVec4`/`FloatVec2` compare **bitwise** — `VecBase::operator==` at `VectorTypes.h:41` is IEEE `==`, under which a NaN patch level would differ from itself; the docs' stated policy is bitwise, `ARCHITECTURE.md:159`). Partial ordering picks these over `const T&`. -- Coverage assertion ("the comparator's coverage is itself asserted", `PipeFields.def:15-17`): `gen_pipe.py` gains `check_field_lists_cover_struct_members()`: for every payload in `MGP_VERIFY_PAYLOAD_LIST`, locate `struct {` in `MGPipeTypes.h`, `MGPipeValueTypes.h`, `MGPipeHostSpan.h`, `MG_Backend/BackendObject.h` (comments/strings masked, `gen_pipe_dirty_surface.py:46-85` shape), collect the direct data members (regex over one-member-per-line bodies; skip `static`, `constexpr`, member functions, nested types; a member named `Pad\d*` is padding), and `sys.exit` on any member absent from the `F(...)` list or any `F(...)` name that is not a member. Runs in both modes (`--check` included) and therefore in `pipe-gates`. Negative control inside `gen_pipe.py --self-test` (new flag): a canned struct/field-list pair that must trip. - -### D9 — The 58 non-arrow lines: exact rewrite per category, and why the pull build does not move - -| category | count | lines | rewrite | pull-build effect | -|---|---|---|---|---| -| `MOBILEGL_ASSERT(MG_State::pGLContext, "...")` | 34 | `DirectVulkan.cpp` 337,343,349,355,362,369,376,383,389,394,445,451,496,523,567,582,616,622,631,638,644,650,656,741,746,752,758,817,842,865,971,977,1001,1008 | `MOBILEGL_ASSERT(MGB_CTX_LIVE, "...")` | none (macro is empty at INFO, `Defines.h:113-115`) | -| `MOBILEGL_ASSERT(MG_State::pGLContext != nullptr, ...)` | 9 | `UniformManager.cpp` 812,837,992,1152,1266,1651,1806,1888,1968 | `MOBILEGL_ASSERT(MGB_CTX_LIVE, ...)` | none | -| bare guard `if (MG_State::pGLContext) {` | 5 | `Managers.cpp` 3644,3844,8750; `BackendObject_DirectVulkan.cpp` 388,786 | `if (MGB_CTX_LIVE) {` | `UniquePtr::operator bool` is `get() != nullptr`; identical codegen expected | -| compound guard | 2 | `Managers.cpp` 3773 (`&& MG_State::pGLContext &&`), 4735 (`if (MG_State::pGLContext && ...`) | `MGB_CTX_LIVE` in place | as above | -| ternary condition `MG_State::pGLContext != nullptr` | 3 | `Managers.cpp` 7192,7200,7203 (their `?` arms 7193,7201,7204 are arrow lines and take the sed) | `MGB_CTX_LIVE` | as above | -| `!= nullptr` in a condition | 2 | `DirectVulkan.cpp` 1236; `VulkanRenderer.cpp` 12766 | `MGB_CTX_LIVE` | as above | -| `== nullptr` | 1 | `VulkanRenderer.cpp` 11267 | `!MGB_CTX_LIVE` | as above | -| `.get()` raw capture + `decltype` alias | 1 (+ arrow line 143) | `DirectGLES.cpp` 143-155 | `:143` → `decltype(MGB_CTX->GetFramebufferBindingSlot(FramebufferTarget::Draw))`; `:144` → `static const void* g_fbSlotCacheContext = nullptr;`; `:147` → `const void* ctx = MGB_CTX_IDENTITY;`; `:150` → `g_fbSlotCache[i] = &MGB_CTX->GetFramebufferBindingSlot(static_cast(i));` | a `const void*` compare instead of `GLContext*`: identical codegen expected; if `.text` moves, this is the first suspect | -| comment | 1 | `VertexInputStateFactory.h:133` | "they live on the frontend context's VAOs" | none | -| ternary with arrow on one line | (arrow line) | `DirectVulkan.cpp` 1284 | `MGB_CTX_LIVE ? MGB_CTX->GetTransformFeedbackPausedPrimitiveCounter() : 0` | as above | - -**[deviation]** `ARCHITECTURE.md:344` says the ~34 asserts are *deleted*. They are converted instead: identical in every INFO build (G1 is unaffected), preserves DEBUG-build parity, and gate C is satisfied equally (no `pGLContext` token remains). Under push, `MGB_CTX_LIVE` is always true once a context exists, which changes the guarded sites' semantics only in the push arm — that is the "空守卫/三元重写推迟到 P2" the gate names: P1 keeps them textually equivalent, P2 gives them real meaning. - -Expected G1 result: `.text` delta **0**, symbols 0/0/0/0. The only code-bearing rewrites are the 13 guard/compare lines and the `.get()` block, and each is a like-for-like pointer test. - -### D10 — `SyncPersistentMappedRange` (20) + `SyncGpuWrites` (6): per-site ownership table - -Reconcile classes are `ARCHITECTURE.md:233-238` (§5.7): **R0** none; **R1** EBO/max-index scan = `SyncPersistentMappedRange` **+** `SyncGpuWrites`; **R2** `*IndirectCount` count = **only** `SyncPersistentMappedRange` (open question #15, `ROADMAP.md:88` — recorded, not fixed); **R3** server-side restart rewrite / multi-draw flatten = server reads its mirror, GPU-writer visibility by `OnGpuWritten`; **R4** UBO host payload, the immovable consumer (D-B8, `ARCHITECTURE.md:294`). In P1 every site stays exactly where it is (the `sed` touches none of these lines); the table records the class each site must reproduce when P8 moves the reconcile to the client. - -| # | site (verified) | receiver | verb | today | class | -|---|---|---|---|---|---| -| P1 | `DirectGLES.cpp:263` | drawBuffer | draw indirect (CPU fallback `ResolveIndirectCommandBytes`) | SPMR only | R2 | -| P2 | `DirectGLES.cpp:4468` (+ G1 `:4469`) | indexBuffer | draw, restart substitution | SPMR + SGW | R1 | -| P3/P4 | `DirectGLES.cpp:4722,4723` | drawBuffer, parameterBuffer | `MultiDrawElementsIndirectCount` | SPMR only | R2 | -| P5/P6 | `DirectGLES.cpp:4824,4825` | drawBuffer, parameterBuffer | `MultiDrawArraysIndirectCount` | SPMR only | R2 | -| P7 | `Managers.cpp:1569` | bufferObject | buffer op (`EnsureBufferResource`) | SPMR | R0 (client array/staging: application memory, no GPU writer) | -| P8 | `MultiDraw.cpp:510` (+ G3 `:511`) | indexBuffer | multi-draw flatten `RunRebasedDrawElements` | SPMR + SGW | R3 | -| P9 | `DirectVulkan.cpp:280` | drawBuffer | draw indirect (CPU fallback) | SPMR only | R2 | -| P10 | `DirectVulkan.cpp:471` | parameterBuffer | `MultiDrawArraysIndirectCount` | SPMR only | R2 | -| P11 | `DirectVulkan.cpp:795` | indexBufferShared | `BuildClosedLineLoopIndices` (index read) | SPMR only | R1 (client pointer source → R0 when the indices are client memory) | -| P12 | `UniformManager.cpp:2023` | bufferObject | `ResolveUniformBufferPayload` | SPMR | **R4** — immovable | -| P13 | `VkBufferManager.cpp:628` | bufferObject | `AcquireResidentSlice` | SPMR | R0 | -| P14 | `VkBufferManager.cpp:679` | bufferObject | `AcquireStreamedSlice` | SPMR | R0 | -| P15 | `VulkanRenderer.cpp:3434` (+ G4 `:3433`) | indexBufferShared | `TryComputeMaxIndexFromHostBytes` | SPMR + SGW | R1 | -| P16 | `VulkanRenderer.cpp:3513` | bufferObject | `TryBindResolvedVertexBindings` | SPMR | R0 | -| P17 | `VulkanRenderer.cpp:3828` (+ G5 `:3827`) | sourceBufferShared | fp64 narrowing in `UploadAndBindVertexBuffers` | SPMR + SGW | R1 (a GPU-written vertex source) | -| P18 | `VulkanRenderer.cpp:7137` | indirectBuffer | `DispatchComputeIndirect` | SPMR only | R2 | -| P19/P20 | `VulkanRenderer.cpp:12132,12133` | drawBuffer, parameterBuffer | `MultiDrawElementsIndirectCount` | SPMR only | R2 | -| G2 | `Managers.cpp:2643` | bufferObject | `SyncFloat64AttributeAsFloat32` | SGW | R1 | -| G6 | `VulkanRenderer.cpp:4161` | indexBufferShared | `UploadAndBindIndexBuffer`, restart branch | SGW | R3 | - -Split: SPMR 8 Espryt / 12 Magma; SGW 3 / 3. Comment-only mentions (not sites): `Managers.cpp:1461,2681`, `VulkanRenderer.h:1214`, `DirectGLES.cpp:459,509`. The integrator lands this table in `docs/Disaggregated/MEASUREMENTS.md` as a new §6. - -### D11 — Deferred out of P1 (recorded, with the doc lines to correct) - -| item | why not P1 | doc to fix | -|---|---|---| -| `gen_pipe_dirty_surface.py` mapping file + `--check` gate | `ROADMAP.md:18` puts "首轮映射成门" in **P2**; the table cells are the authority (scout R2) | `ARCHITECTURE.md:172,568`, `test.yml:871-874` comment, `scripts/gen_pipe_dirty_surface.py:21-22` say P1 → P2 (integrator) | -| `GetBufferBindingSlot` rows split by target in `Coverage.def` (`:36-40`) | needs the re-vendored inventory carrying the target argument; the extractor lives in MobileGL-CS, not here | note in `Coverage.def:36-40` ("when the inventory is re-vendored") | -| re-vendoring `scripts/data/backend_read_inventory.md` | same; D13's live cross-check is the stop-gap gate | `MEASUREMENTS.md:82` | -| `DynamicBackendParameters` fixed-width members (`MGPipeTypes.h:32`, scout R6) | not in the P1 row; the field list (D8) covers verify without it | — | -| semantic rewrite of the 13 guard/ternary sites | gate text: "推迟到 P2" | — | -| `MGPipeImpl_*.cpp` | D5 | `ARCHITECTURE.md:553` | -| `PipeStats` `AccessorCalls` tallies (`PipeStats.cpp:16-100`) | unchanged: the sites still call the same accessors through `MGB_CTX`; `RecordDrawPayloadBytes` stays unwired until a generator emits records | — | - -### D12 — Coverage.def and the live accessor cross-check (A) - -`Coverage.def` gains two rows after `:97`: `X(GetBoundTransformFeedbackLifetimeId, SetStreamOutputTargets)` and `X(HasOpenTransformFeedbackSpan, SetStreamOutputTargets)` (the D21 XFB counter-slot rekey's reads, `VulkanRenderer.cpp:11219,11236`; the calls they map to are the same as `GetTransformFeedbackGeneration`'s); `GetBoundTransformFeedbackName` keeps its row with a `/* dead: no backend reads it since D21; kept for inventory row 594 */` comment. `gen_pipe.py` gains `scan_live_accessors()`: walk `MobileGL/MG_Backend/**.{cpp,h}` (comments masked), collect every `\b(?:MGB_CTX|pGLContext)\s*->\s*(\w+)` name, `sys.exit` if a name has no `Coverage.def` row, and print (informational) rows no backend reads. Runs in both modes, so `pipe-gates` catches the next drift the day it lands. Zero-row accessors are already fine for `gen_coverage` (`gen_pipe.py:513-579` only iterates inventory rows). - -### D13 — Namespaces, naming, generated-file discipline - -Pipe code: `namespace MobileGL::MG_Pipe` (`MGPipe.h:26`); `PipeInputs`, `gPipeInputs`, `MGPipeVerb`, `MGPipeVerbClass`, `MGPipeFieldMask`, `kMGPipe*` constants, `MGPipeFillForVerb`, `SnapshotFromGLContext`, `MGPipeVerifyInputs`, `MGPipeVerifyReadHook`, `MGPipeInputsFieldEqual`. Generated files: `MobileGL/MG_Pipe/generated/*.inc`, LF, written through `write()` inside `main()` (`gen_pipe.py:619-652`) so `--check` and the CI `git diff -- MobileGL/MG_Pipe/generated` see them. Banner: `MGPipe.h:1-7`. Commits: `[Type] (Scope): description`, single line, no `Co-Authored-By`. Logging per D8. Test names: additions only. - ---- - -## C. Packages - -Four packages on four branches, four WSL worktrees. **Branch points:** `p1/core` from `feat/disaggregated@087685d1`. `p1/core`'s **first commit is the contract** (`c1`, tagged `p1/contract`): the switch header, `PipeInputs.h` with every accessor, `PipeFill.h`, `FillPoints.def` + regenerated `PipeFillPoints.inc`, the CMake options, `PipeInputs.cpp`/`PipeFill.cpp` with the forwarders and a `MGPipeFillForVerb` that only bumps (no field copies yet), `Config.h`/`ConfigLoader.cpp` knobs. `p1/espryt`, `p1/magma`, `p1/verify` branch from `p1/contract`, so their push builds compile and link while A finishes. `c1` is byte-neutral for the pull build (headers nobody includes, sources behind an OFF option). - -WSL tree creation (copy `scratchpad/wf2/wsl_p05_trees.sh` → `wsl_p1_trees.sh`; `p05`→`p1`, slugs `core espryt magma verify`; the three non-core trees are created after `p1/contract` exists with `git worktree add -q ~/w7/p1-$slug -b p1/$slug p1/contract`). Each tree configures three build directories: - -``` -# pull (the G1 build; flags identical to ~/w7/pipe/build-linux) -cmake -S . -B build-linux -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_BUILD_TYPE=Release -DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO -DMOBILEGL_BUILD_TEST=ON -DMOBILEGL_BUILD_INTEGRATION_TEST=ON -DMOBILEGL_ITEST_EGL_VENDOR=/usr/share/glvnd/egl_vendor.d/50_mesa.json -DMOBILEGL_ITEST_VK_ICD=/usr/share/vulkan/icd.d/lvp_icd.json -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -# push -cmake -S . -B build-push ... same ... -DMOBILEGL_PIPE_PUSH=ON -# verify (A and E, and the integrator) -cmake -S . -B build-verify ... same ... -DMOBILEGL_PIPE_VERIFY=ON -DMOBILEGL_ITEST_REQUIRE_GPU=ON -``` - -Before anything else the integrator captures the baseline: `cp ~/w7/pipe/build-linux/libMobileGL.so ~/w7/p1-before-libMobileGL.so` (confirm `git -C ~/w7/pipe log -1` is `087685d1` and the cache flags above), `ctest --test-dir ~/w7/pipe/build-linux -N | grep -E '^\s+Test #' | sed 's/^ *Test *#[0-9]*: //' | sort > ~/w7/p1-before-ctest-names.txt`, `grep -rc "pGLContext" MobileGL/MG_Backend | sort > ~/w7/p1-before-pglcontext.txt` (must equal the D1 per-file counts). - -### C.1 Package A — `p1-core` (tree `~/w7/p1-core`, branch `p1/core`) - -**Files** - -| action | file | what | -|---|---|---| -| CREATE | `MobileGL/MG_Pipe/PipeInputsSwitch.h` | D3 verbatim | -| CREATE | `MobileGL/MG_Pipe/FillPoints.def` | D7: three macros, 69 verb rows in `GLFunctionsTable` declaration order, 9 classes, the field rows; blank line after each macro | -| CREATE | `MobileGL/MG_Backend/MGPipe/PipeInputs.h`, `PipeInputs.cpp` | D4, D6, D8 | -| CREATE | `MobileGL/MG_Impl/Pipe/PipeFill.h`, `PipeFill.cpp` | D7, D8; `PipeFill.cpp` includes ``, ``, `` | -| MODIFY | `MobileGL/MG_Pipe/Coverage.def` | D12 rows; `MGP_COVERAGE_STICKY_LIST(X)` with the seven D6 rows and reasons; note at `:36-40` | -| MODIFY | `MobileGL/MG_Pipe/PipeFields.def` | D8: six new field lists + payload-list entries | -| MODIFY | `scripts/gen_pipe.py` | parse `FillPoints.def` (`parse_fill_points()`, blank-line-terminated blocks, `sys.exit` on: a verb not in `GLFunctionsTable`, a table member without a row, a verb in two classes, a class with no verbs, an unknown field, a duplicate (class, field)); parse `MGP_COVERAGE_STICKY_LIST`; `gen_filled` emits real sticky flags; `gen_fill_points` → `PipeFillPoints.inc` written via `write()` in `main()` between the `PipeFilled.inc` and `PipeCoverage.inc` lines (`:650-651`); `gen_verify` overloads + empty `MEMCMP_FALLBACK_TYPES` + `static_assert` fallback; `check_field_lists_cover_struct_members()`; `scan_live_accessors()`; `--self-test` (canned negative controls for the struct-member check and the verb-set check; `trips == 0 → error`, `check_include_closure.py:541-543` shape); summary line gains "N verbs, M classes" | -| MODIFY | `MobileGL/MG_Pipe/generated/*.inc` | regenerate and commit (7 files change: `PipeFilled.inc` count 63 + sticky, `PipeVerify.inc`, new `PipeFillPoints.inc`, the banner of any file whose sources line changes) | -| MODIFY | `MobileGL/MG_Pipe/MGPipe.h` | `#include "generated/PipeFillPoints.inc"` after `:86` with a G5b comment | -| MODIFY | `CMakeLists.txt` | after `:24`: `option(MOBILEGL_PIPE_PUSH "Backends read frontend state through the MGPipe PipeInputs block instead of MG_State::pGLContext (ARCHITECTURE.md 9.2 phase A)" OFF)`, `option(MOBILEGL_PIPE_VERIFY "Compile SnapshotFromGLContext() and the G4 per-verb shadow comparator; implies MOBILEGL_PIPE_PUSH; never shipped" OFF)`; after `:451`: `if (MOBILEGL_PIPE_VERIFY AND NOT MOBILEGL_PIPE_PUSH) message(STATUS ...) set(MOBILEGL_PIPE_PUSH ON) endif()`; `if (MOBILEGL_PIPE_PUSH) list(APPEND SOURCE_FILES MobileGL/MG_Backend/MGPipe/PipeInputs.cpp MobileGL/MG_Impl/Pipe/PipeFill.cpp) endif()`; after `:525`: the two `list(APPEND MOBILEGL_COMPILE_DEF -DMOBILEGL_PIPE_PUSH=1 / -DMOBILEGL_PIPE_VERIFY=1)` | -| MODIFY | `MobileGL/Config.h:332` region, `MobileGL/ConfigLoader.cpp:246` region | D2's three knobs under `#if MOBILEGL_PIPE_PUSH` | -| MODIFY | `MobileGL/MG_Impl/GLImpl/{Drawing/GL_Drawing.cpp, Framebuffer/GL_Framebuffer.cpp, Texture/GL_Texture.cpp, Getter/GL_Getter.cpp, Program/GL_Program.cpp, Query/GL_Query.cpp, Sync/GL_Sync.cpp}` | `#include ` after each TU's last `MG_State`/`MG_Backend` include; the 83 `MGP_FILL(X);` statements of §B.7 | -| MODIFY | `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp` | `:225` 61 → 63; new tests below; if the verify-only tests need it, `#if MOBILEGL_PIPE_VERIFY … #else GTEST_SKIP() << "verify not compiled in" #endif` (a visible skip, not a vanishing test) | -| MODIFY | `MobileGL/MG_Test/Pipe/CMakeLists.txt` | only if a `PipeInputs`-linking test target is added: `PipeInputsTest` linking `${LINK_LIBRARIES}` (the static library), registered `LABELS unit` | - -**Ordered steps** - -1. `c1` (contract): switch header; `FillPoints.def` + generator growth + regenerate; `PipeInputs.h` complete (all 63 accessors, storage, macros expanding to nothing until step 3); `PipeInputs.cpp` (equality, corruption injector); `PipeFill.h`; `PipeFill.cpp` with `IsLive`, `ContextIdentity`, the seven forwarders, and `MGPipeFillForVerb` = serial bump + `m_currentVerb` only; CMake options and conditional sources; `Config.h`/`ConfigLoader.cpp` knobs; `Coverage.def` + `PipeFields.def` edits; `PipeCatalogueTest.cpp` count. Build pull (identical), push, verify; commit; **tag `p1/contract`** and tell the other packages. Message: `[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`. -2. `c2`: the filler proper (per-class copies, stamps, omission knob), poison checks in the accessors (`MGP_INPUT_CHECK`), sticky stamping on first live fill. Message: `[Feat] (Pipe): fill PipeInputs per verb class from GLContext and stamp per-verb generations - a read of a field the verb did not fill is Fatal{UnmigratedPipeInput}`. -3. `c3`: the 83 fill points in `MG_Impl`. Message: `[Feat] (Impl): call MGP_FILL before every GLFunctionsTable entry - 83 statements over 69 verbs, a no-op in the pull build`. -4. `c4`: `SnapshotFromGLContext`, entry compare, compare-at-read, arming line, `CORRUPT`/`FATAL` knobs. Message: `[Feat] (Pipe): the MOBILEGL_PIPE_VERIFY shadow comparator - a per-verb entry compare over the fill set and a compare-at-read in every accessor, first differing field and verb serial, fatal by default`. -5. `c5`: G4 field lists for the six value types, the two overloads, the struct-member coverage check, `scan_live_accessors`, `--self-test`; regenerate. Message: `[Feat] (Pipe): give the six value structs G4 field lists and assert the lists cover their members - the memcmp fallback is now a compile error, floats in vector types compare bitwise`. -6. `c6`: unit tests. Message: `[Test] (Pipe): pin the P1 poison and verify shapes - 63 fields, 69 verbs, the seven sticky fields, an omitted GenerateMipmap field leaves exactly that field stale, a corrupted snapshot names its field, reading an unfilled field aborts with SIGABRT`. - -**Tests to add** (`PipeCatalogueTest.cpp`; the fork/waitpid one follows `HeadlessGL.cpp:343-388` / `FdPassingTest.cpp:62-132`, never `EXPECT_DEATH` — the tree has none): -- `PipeInputFieldsStartUnfilled`: 63. -- `VerbTableIsTheFunctionTable`: `kMGPipeVerbCount == 69`; every class non-empty; the seven sticky bits are set in every class mask. -- `StickyFieldsAreExactlyTheSeven`: iterate `kMGPipeInputFieldSticky[]`. -- `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale` (scout B4 layer 1, verbatim shape): fill twice with a fake context (`MG_State::pGLContext = MakeUnique()`, the `SanityTest.cpp:907` idiom), once clean, once with `SetPoisonOmission("GenerateMipmap", "GetActiveTextureUnit")`; assert `GetTextureUnitObject` fresh and `GetActiveTextureUnit` stale, and that a `DrawArrays` fill afterwards leaves `GetActiveTextureUnit` stale (not in `kDraw`'s mask) while `GetBoundVertexArray` is fresh. -- `ReadingAnOmittedFieldAbortsNamingTheVerb` (`#if MOBILEGL_PIPE_POISON` else `GTEST_SKIP`): fork; child fills `GenerateMipmap` with the omission then calls `gPipeInputs.GetActiveTextureUnit()`; parent expects `WIFSIGNALED && WTERMSIG == SIGABRT`; sibling test without the omission expects `_exit(0)`. Log assertion of the exact `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}` string via `MOBILEGL_LOG_FILE_PATH` set in the test's own `main()` (`LogLevelTest` shape, `MG_Test/Util/CMakeLists.txt:19-22`) — if that needs a separate target, `PipeInputsTest` links `GTest::gtest`. -- `CorruptedSnapshotFieldIsNamedWithItsSerial` (`#if MOBILEGL_PIPE_VERIFY`): clean arm true; `ApplyVerifyCorruption(snapshot, "GetRenderStateParameters")` → false, field string equal, serial equal to the fill serial. -- `FloatVectorsCompareBitwise`: `MGPipeFieldEqual(FloatVec4{NaN,...}, same bits)` true; `-0.0f` vs `0.0f` false; `Array` differing in element 3 → `MGPipeVerify(ResidualValueBlock…)` names `RenderState`. -- `SixValueStructsHaveFieldLists`: `kMGPipeVerifiedPayloadCount == 69u`. - -**Constraints**: `PipeInputs.h` and `PipeInputs.cpp` never spell `pGLContext` (`grep -c pGLContext MobileGL/MG_Backend/MGPipe/*` == 0). `MG_Backend/DirectGLES/**` and `MG_Backend/DirectVulkan/**` are **not touched** (B and C own them). `PipeCalls.def` untouched. No renames of `PipeStats` counter names (`PipeStatsTest.cpp:260-279`). No `MGLOG_D` in verify reporting. - -**Verification (WSL, `~/w7/p1-core`)** -``` -python3 scripts/gen_pipe.py --check && python3 scripts/gen_pipe.py --self-test -cmake --build build-linux --parallel 28 && ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 # 0/0/0/0, .text +0 -grep -rc "pGLContext" MobileGL/MG_Backend | sort | diff - ~/w7/p1-before-pglcontext.txt # empty (A converts no site) -grep -c "pGLContext" MobileGL/MG_Backend/MGPipe/PipeInputs.h MobileGL/MG_Backend/MGPipe/PipeInputs.cpp # 0 0 -grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp | awk -F: '{s+=$2} END {print s}' # 83 -cmake --build build-push --parallel 28 && ctest --test-dir build-push -L unit --no-tests=error -cmake --build build-verify --parallel 28 && ctest --test-dir build-verify -L unit --no-tests=error -# the poison is real: with the sites still on pGLContext (B/C not merged) nothing reads gPipeInputs, so run the -# integration suite in build-push to prove the fill points and filler are inert-correct (all green, no aborts): -ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4 -ctest --test-dir build-linux -N | grep -E '^\s+Test #' | sed 's/^ *Test *#[0-9]*: //' | sort | comm -23 ~/w7/p1-before-ctest-names.txt - # empty -``` - -### C.2 Package B — `p1-espryt` (tree `~/w7/p1-espryt`, branch `p1/espryt`, from `p1/contract`) - -**Files**: `MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp`, `Managers.cpp`, `MultiDraw.cpp`, `Utils.cpp` (nothing else; `Managers.h`, `Utils.h`, `DirectGLES.h`, `BackendObject_DirectGLES.cpp` have no sites). - -**Site list it owns** — 122 lines = 113 arrow lines (113 occurrences) + 9 non-arrow: - -| file | arrow lines (sed `MG_State::pGLContext->` → `MGB_CTX->`) | non-arrow (D9) | -|---|---|---| -| `DirectGLES.cpp` (91 + 1) | 143,261,357,370,429,462,465,474,481,518,662,892,987,1007,1244,1275,1373,1386,1414,1415,1515,1517,1518,1580,1728,1820,2026,2054,2083,2166,2842,2844,2846,2957,2968,3048,3212,3362,3482,3580,3662,3712,3821,3822,3826,4062,4088,4093,4156,4215,4380,4396,4397,4402,4442,4570,4609,4680,4711,4712,4782,4813,4814,4973,5014,5259,5260,6044,6045,6107,6108,6185,6375,6699,6700,6794,6795,6932,6933,7357,7358,7554,7562,8544,8774,9041,9084,9194,9197,9420,9510 | 147 (`.get()`, with the 143-155 block rewrite of D9) | -| `Managers.cpp` (15 + 8) | 3645,3646,3774,3775,3845,3846,4736,4737,5355,5460,6230,7193,7201,7204,8751 | 3644,3773,3844,4735 (guards), 7192,7200,7203 (ternary conditions), 8750 (guard) | -| `MultiDraw.cpp` (5) | 45,51,52,87,95 | — | -| `Utils.cpp` (2) | 2297,2301 | — | - -**Ordered steps** -1. Add `#include ` (D3 placement) to the four TUs. -2. Mechanical sed over the four files: `sed -i 's/MG_State::pGLContext->/MGB_CTX->/g'` — then confirm the per-file occurrence counts above (`grep -c 'MGB_CTX->'`), and that the only remaining `pGLContext` tokens are the 9 non-arrow lines. -3. Convert the 9 non-arrow lines per D9 (the `DirectGLES.cpp:143-155` block by hand). -4. Build pull; symbol report; build push; run. -5. One commit: `[Refactor] (Espryt): route every frontend read through MGB_CTX - 113 arrow sites sed'd, 9 non-arrow lines converted (the fb-slot cache keys on MGB_CTX_IDENTITY); pull build byte-identical`. - -**Constraints**: no reordering, no reformatting beyond the touched lines; the `PipeStats` `AddCalls` literals near the sites (`DirectGLES.cpp:1416-2969`, `Managers.cpp:783-4433`, `MultiDraw.cpp:176-754`) are unchanged; the 8 + 3 sync sites of D10 are untouched; no new `pGLContext` spelling anywhere. - -**Verification (WSL, `~/w7/p1-espryt`)** -``` -grep -rc "pGLContext" MobileGL/MG_Backend/DirectGLES | grep -v ":0$" # empty -grep -c "MGB_CTX" MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp MobileGL/MG_Backend/DirectGLES/Managers.cpp MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp MobileGL/MG_Backend/DirectGLES/Utils.cpp # ≥ 92+23+5+2 (occurrences per file: DirectGLES 91 arrow + 4 in the cache block + 1 alias; Managers 15+8; MultiDraw 5; Utils 2) -cmake --build build-linux --parallel 28 && ctest --test-dir build-linux -L unit --no-tests=error -python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 # 0/0/0/0, .text +0 expected; any delta attributed to D9 lines in the commit message -ctest --test-dir build-linux -L integration-gpu -R '^DirectGLES\.' --no-tests=error -j 4 # by-name green -cmake --build build-push --parallel 28 && ctest --test-dir build-push -L integration-gpu -R '^DirectGLES\.' --no-tests=error -j 4 # links; runs (on the contract tree the filler does not copy yet, so under push this run is expected to hit Fatal{UnmigratedPipeInput} at the first read - that is the poison working; rerun after rebasing onto p1/core c2+ and it must be green) -``` - -### C.3 Package C — `p1-magma` (tree `~/w7/p1-magma`, branch `p1/magma`, from `p1/contract`) - -**Files**: `MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp`, `BackendObject_DirectVulkan.cpp`, `Renderer/UniformManager.cpp`, `Renderer/VulkanRenderer.cpp`, `Renderer/VkClearManager.cpp`, `Renderer/VkRenderPassManager.cpp`, `Renderer/VkTextureManager.cpp`, `Renderer/VertexInputStateFactory.h` (comment only). - -**Site list it owns** — 213 lines = 164 arrow occurrences on 161 lines + 49 non-arrow: - -| file | arrow lines | non-arrow (D9) | -|---|---|---| -| `DirectVulkan.cpp` (12 + 35) | 270,273,278,402,465,533,586,716,786,910,1237,1284 (1284 also gets the `MGB_CTX_LIVE ?` rewrite) | 337,343,349,355,362,369,376,383,389,394,445,451,496,523,567,582,616,622,631,638,644,650,656,741,746,752,758,817,842,865,971,977,1001,1008 (asserts), 1236 (`!= nullptr`) | -| `BackendObject_DirectVulkan.cpp` (2 + 2) | 389,787 | 388,786 (guards) | -| `UniformManager.cpp` (14 + 9) | 506,555,782,821,847,1016,1189,1194,1295,1662,1850,1938,2013,2018 | 812,837,992,1152,1266,1651,1806,1888,1968 (asserts) | -| `VkClearManager.cpp` (1) | 57 | — | -| `VkRenderPassManager.cpp` (3) | 613,965,1111 | — | -| `VkTextureManager.cpp` (2) | 809,952 | — | -| `VulkanRenderer.cpp` (127 lines / 130 occurrences + 2) | 463,468,523,556,557,570,649,650,1243,1247,1303,2771,2988,3448,3449,3926,4060,4815,4816,4817,4828,4940,4984,5172,5173,5175,5178,5180,5181,5187,5199,5200,5202,5287,5288,5294,5298,5312,5317,5318,5354,5355,5407,5435,5436,5437,5442,5858,5919,5943,6019,6043,6091,6105,6110,6121,6122,6130,6304,6434,6464,6471,6472,6512,6526,6532,6650,6669,6830,6831,6972,6973,6999,7036,7088,7132,7206,7209,7257,7260,7268,7269,7270,7277,7300,7327,7340,7369,7373,7389,7410,7439,7481,7512,7514,7526,7583,7601,7609,7628,8586,8587,8619,8620,9214(×2),9222,9933,10688,10689,10719(×2),10963(×2),11219,11236,11268,11274,11277,11316,11347,11370,12018,12096,12106,12112,12197,12207,12277,12767 | 11267 (`== nullptr`), 12766 (`!= nullptr`) | -| `VertexInputStateFactory.h` (0 + 1) | — | 133 (comment) | - -Steps, constraints and verification exactly as B, with `DirectVulkan` in place of `DirectGLES`, the seven TUs getting the switch include (D3 placement), and one commit: `[Refactor] (Magma): route every frontend read through MGB_CTX - 164 arrow sites sed'd, 49 non-arrow lines converted (43 asserts keep their meaning as MGB_CTX_LIVE); pull build byte-identical`. The 12 + 3 sync sites of D10 are untouched. `grep -rc "pGLContext" MobileGL/MG_Backend/DirectVulkan | grep -v ":0$"` is empty. - -### C.4 Package E — `p1-verify` (tree `~/w7/p1-verify`, branch `p1/verify`, from `p1/contract`) - -The third CI mode and the negative controls. Everything E codes against is a contract fixed above: option names (D2), env knobs (D2), log lines (D8), the `MGPipeVerb`/field vocabularies (D7/D4), `MGLOG_F` + `std::abort()` = SIGABRT. - -**Files** - -| action | file | what | -|---|---|---| -| MODIFY | `MobileGL/MG_IntegrationTest/CMakeLists.txt` | after `:393`: `set(MGL_ITEST_VERIFY_TIMEOUT 900)`; `if (MOBILEGL_PIPE_VERIFY)` block with two ambient registrations `DirectGLES.Verify.` / `DirectVulkan.Verify.` (env `MOBILEGL_PIPE_VERIFY=1` + `MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-.log` + **`${MGL_ITEST_COMMON_ENV}` / `${MGL_ITEST_VULKAN_ENV}` appended** (`:339-344` rule), `LABELS integration-gpu integration-verify`, `TIMEOUT ${MGL_ITEST_VERIFY_TIMEOUT}`, joined with `mgl_itest_join_environment` (`:307-317`)); two corrupted-field lanes `DirectGLES.VerifyCorrupted.` / `DirectVulkan.VerifyCorrupted.` (`TEST_FILTER "PipeVerifyArmingScenario.CorruptedFieldIsReported"`, env adds `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters MOBILEGL_PIPE_VERIFY_FATAL=0`, own log path); two omission lanes `DirectGLES.PoisonOmitted.` / `DirectVulkan.PoisonOmitted.` (`TEST_FILTER "PoisonOmissionScenario.*"`, env adds `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit`, own log path). Both scenario files added to the `add_executable` list (`:51-130`) | -| CREATE | `MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp` | `Armed`: skips unless `AmbientQuirkFromEnvironment("MOBILEGL_PIPE_VERIFY") == On` (`ScenarioFixture.h:56-65`) and the log path is set (`UnlocatedIoBlockScenario.cpp:354-360` idiom, only bytes written after the case started); draws once; asserts `MGPipe: verify armed` present and no `Fatal{PipeVerifyDiffer`. `CorruptedFieldIsReported`: skips unless `MOBILEGL_PIPE_VERIFY_CORRUPT` is set; draws once; asserts `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@` present | -| CREATE | `MobileGL/MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp` | `OmittedFieldAbortsOnThatVerb`: skips unless `MOBILEGL_PIPE_POISON_OMIT` is set; `fork()` **before** any scenario-local GPU work (`HeadlessGL.cpp:343-388`, `_exit` never `exit`, `:365-368`); child: create a 2-level texture, `glDrawArrays` once (must not abort), `glGenerateMipmap` (must abort), `_exit(0)`; parent: `WIFSIGNALED && WTERMSIG == SIGABRT`, log carries `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}` and no `@DrawArrays`. `WithoutOmissionCompletes`: same child without the knob → `WIFEXITED && WEXITSTATUS == 0` (registered in the same lane by filter; the env var is present, so this case clears it? No — env is process-wide: the clean arm runs in the ambient `Verify.` lanes instead, where the scenario asserts the knob is unset) | -| MODIFY | `tools/trace_replay/run_trace_case.cmake` | after `:129`: `if(DEFINED ENV{MOBILEGL_PIPE_VERIFY} AND NOT "$ENV{MOBILEGL_PIPE_VERIFY}" STREQUAL "0")` → `FATAL_ERROR` if `mobilegl.log` missing, if it lacks `MGPipe: verify armed`, or if it contains `Fatal\{PipeVerifyDiffer` / `Fatal\{UnmigratedPipeInput` (scout B2.2 text) | -| MODIFY | `tools/trace_replay/CMakeLists.txt:373-379` | add `LABELS retrace` to both `set_tests_properties` (the lane is otherwise selectable only by regex) | -| MODIFY | `tools/trace_replay/trace_cases.json`, `trace_cases.py` | `"verify": true` on eight cases: the five 180 s cases plus `minecraft-1.21.4-in-world`, `minecraft-1.21.4-fabric-sodium-in-world`, `improved-transparency-minecraft-26.3` (exact names from the file; E lists them in the commit); `--format github-verify-matrix` = `github_test_matrix(cases with verify)`; unit-check in `trace_cases.py`'s existing self-checks | -| MODIFY | `scripts/symbol_report.py` | implement `--fail-on-added-bytes N` (`:287-288` is reserved) and add `--fail-on-symbol-set-change`; default behaviour unchanged; `--self-test` covers both | -| MODIFY | `.github/workflows/test.yml` | (a) `build-linux-verify` (copy of `build-linux:12-147` with `BUILD_DIR=build-verify`, its own ccache key `${{ runner.os }}-test-${{ github.job }}-ccache-v1`, configure adds `-DMOBILEGL_PIPE_VERIFY=ON` and keeps `MOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_INFO`, artifact `mobilegl-linux-runtime-verify`, `timeout-minutes: 120`); (b) `integration-verify` (`needs: build-linux-verify`; the `integration:205-296` shape incl. the path-normalisation step over `build-verify`, `ctest --output-on-failure -L integration-verify --no-tests=error`, core-dump upload, `timeout-minutes: 180`) plus the two negative-control steps: `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters ctest -L integration-verify -R 'DirectGLES\.Verify\..*ClearThenReadPixels' --no-tests=error` must **fail** (`if ctest ...; then echo "::error::..."; exit 1; fi`), and `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit ctest -L integration-verify -R 'DirectGLES\.Verify\..*Mipmap' --no-tests=error` must fail (job env reaches the process: no `ENVIRONMENT` property names these, `test.yml:274-288` precedent); (c) `retrace-verify` (`needs: [build-linux-verify, build-retrace, trace-cases, trace-fixtures]`, matrix from a new `trace-cases` output `verify-matrix` = `--format github-verify-matrix`, `max-parallel: 4`, `timeout-minutes: 240`; unpack the **verify** runtime so the verify `.so` lands at `build-linux/libMobileGL.so` (the path frozen into `CTestTestfile.cmake`), `test -f` + `nm -D build-linux/libMobileGL.so | grep -q MGPipeVerifyInputs`, `export MOBILEGL_PIPE_VERIFY=1` next to the per-case exports (`:700-723`), `ctest -V --no-tests=error --timeout 10800 -R '^MobileGLTraceReplay\.\.$'`, artifacts suffixed `-verify`); one inverted `OpenRA` step with `MOBILEGL_PIPE_VERIFY_CORRUPT`; (d) `remove-artifact-clutter:787` `needs: [retrace-summary, retrace-verify]`; (e) `monolith-symbol-report` (`workflow_dispatch` input `baseline_sha`, default `087685d1`): two pull builds with identical flags (`-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF -DMOBILEGL_BUILD_INTEGRATION_TEST=OFF -DMOBILEGL_BUILD_TRACE_REPLAY=OFF -DMOBILEGL_BUILD_DISAGGREGATED=OFF -DMOBILEGL_PIPE_PUSH=OFF -DMOBILEGL_PIPE_VERIFY=OFF -DMOBILEGL_ENABLE_LTO=OFF`, clang-20), `symbol_report.py --threshold 0 --fail-on-symbol-set-change --fail-on-added-bytes 0 --markdown --json`, upload the markdown, and `nm --defined-only build-sym-head/libMobileGL.so | grep -q MG_Remote && exit 1` (`ARCHITECTURE.md:506`); (f) the `pipe-gates` dirty-surface comment `:871-872` → "in P2"; (g) `pipe-gates` gains `python3 scripts/gen_pipe.py --self-test` after `:851`. Triggers (`:3-9`) unchanged (P0.5 D10). | - -**Constraints**: every new `ENVIRONMENT` list appends `${MGL_ITEST_COMMON_ENV}`/`${MGL_ITEST_VULKAN_ENV}`; the 11 filtered mode lanes are not doubled; no `ENVIRONMENT` property ever names `MOBILEGL_PIPE_VERIFY_CORRUPT`/`MOBILEGL_PIPE_POISON_OMIT` on the ambient `Verify.` lanes (the CI negative-control steps rely on job env reaching them); the verify lanes are registered only under `if (MOBILEGL_PIPE_VERIFY)` so `--no-tests=error` makes a mis-configured lane red; `MG_Test/**`, `MobileGL/**` outside `MG_IntegrationTest/`, `CMakeLists.txt` (root) untouched. - -**Verification (WSL, `~/w7/p1-verify`)** -``` -python3 -c "import yaml; yaml.safe_load(open('.github/workflows/test.yml'))" -python3 tools/trace_replay/trace_cases.py --ci --format github-verify-matrix | python3 -c "import json,sys; m=json.load(sys.stdin); print(len(m['include']))" # 16 (8 cases x 2, minus any ci_backends restriction) -python3 scripts/symbol_report.py --self-test -cmake -S . -B build-verify ... -DMOBILEGL_PIPE_VERIFY=ON -DMOBILEGL_ITEST_REQUIRE_GPU=ON && cmake --build build-verify --parallel 28 -ctest --test-dir build-verify -N -L integration-verify | grep -c 'Test #' # 742 + 2 + 2 (the 371 x 2 ambient entries plus the four control entries) -ctest --test-dir build-verify -N -L integration-gpu | sed 's/\.Verify\././' | sort -u | wc -l # parity with the pull registration set -# On the contract tree the library never arms (the comparator lands in p1/core c4), so the arming case must SKIP-or-FAIL, never pass: -ctest --test-dir build-verify -R 'DirectGLES\.Verify\.PipeVerifyArmingScenario' --output-on-failure # expected red (REQUIRE_GPU on) - the lane is falsifiable -``` -Full functional verification of E happens on the integrated tree (D.3). - -### C.5 File-ownership table (no two packages edit the same file) - -| file / glob | A core | B espryt | C magma | E verify | integrator | -|---|---|---|---|---|---| -| `MobileGL/MG_Pipe/{PipeInputsSwitch.h, FillPoints.def, Coverage.def, PipeFields.def, MGPipe.h, generated/*.inc}` | owner | – | – | – | – | -| `scripts/gen_pipe.py` | owner | – | – | – | – | -| `MobileGL/MG_Backend/MGPipe/**` (new) | owner | – | – | – | – | -| `MobileGL/MG_Impl/Pipe/**` (new), `MobileGL/MG_Impl/GLImpl/{Drawing,Framebuffer,Texture,Getter,Program,Query,Sync}/*.cpp` | owner | – | – | – | – | -| root `CMakeLists.txt`, `MobileGL/Config.h`, `MobileGL/ConfigLoader.cpp` | owner | – | – | – | – | -| `MobileGL/MG_Test/Pipe/**` | owner | – | – | – | – | -| `MobileGL/MG_Backend/DirectGLES/{DirectGLES.cpp, Managers.cpp, MultiDraw.cpp, Utils.cpp}` | – | owner | – | – | – | -| `MobileGL/MG_Backend/DirectVulkan/{DirectVulkan.cpp, BackendObject_DirectVulkan.cpp, Renderer/UniformManager.cpp, Renderer/VulkanRenderer.cpp, Renderer/VkClearManager.cpp, Renderer/VkRenderPassManager.cpp, Renderer/VkTextureManager.cpp, Renderer/VertexInputStateFactory.h}` | – | – | owner | – | – | -| `MobileGL/MG_IntegrationTest/**`, `tools/trace_replay/{run_trace_case.cmake, CMakeLists.txt, trace_cases.json, trace_cases.py}`, `scripts/symbol_report.py`, `.github/workflows/test.yml` | – | – | – | owner | – | -| `docs/Disaggregated/*.md`, `scripts/gen_pipe_dirty_surface.py` (comment), `test.yml:871-872` comment (E lands it) | – | – | – | – | owner | -| everything else | nobody | nobody | nobody | nobody | nobody | - -The contract (`p1/contract`) is the only shared code, and it is a single commit all three other branches descend from; the integrator's rebase of B/C/E onto the final `p1/core` therefore never sees an edit on both sides of one file. - ---- - -## D. Integration order and what the integrator runs - -D.0 Preconditions: baseline captures of §C (`~/w7/p1-before-*`); `~/w7/pipe` clean at `087685d1`; fixture binaries hidden as in `wsl_p05_trees.sh`. - -D.1 Order: **core → espryt → magma → verify.** Copy `scratchpad/wf2/wsl_integrate_p05.sh` → `wsl_integrate_p1.sh` (`p05`→`p1`); per slug: rebase `p1/` onto `feat/disaggregated` in its worktree, `git merge --ff-only`, then in `~/w7/pipe`: -``` -cmake --build build-linux --parallel 28 && ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -python3 scripts/gen_pipe.py --check && python3 scripts/gen_pipe.py --self-test -python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 --markdown ~/w7/p1-symbol-.md # 0/0/0/0 and .text +0 after every merge -``` -After espryt and magma additionally: `grep -rc "pGLContext" MobileGL/MG_Backend | grep -v ":0$"` → empty (G2); `ctest --test-dir build-linux -L integration-gpu --no-tests=error -j 4` green by name (`comm -23 ~/w7/p1-before-ctest-names.txt` empty). - -D.2 Push build on the integrated tree (`~/w7/pipe/build-push`, `-DMOBILEGL_PIPE_PUSH=ON`): unit + `integration-gpu` green — no `Fatal{UnmigratedPipeInput}` is possible here (the poison is off in a Release/INFO push build without VERIFY), so this run proves only that the filler and accessors are functionally equivalent under pull semantics. - -D.3 Verify build (`~/w7/pipe/build-verify`, `-DMOBILEGL_PIPE_VERIFY=ON -DMOBILEGL_ITEST_REQUIRE_GPU=ON`): -``` -ctest --test-dir build-verify -L unit --no-tests=error -ctest --test-dir build-verify -L integration-verify --no-tests=error -j 4 --output-on-failure # 746 entries green: G3 half one -grep -c "MGPipe: verify armed" build-verify/MobileGL/MG_IntegrationTest/pipe-verify-*.log # > 0 in both -MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters ctest --test-dir build-verify -L integration-verify -R 'DirectGLES\.Verify\..*ClearThenReadPixels' ; echo rc=$? # non-zero: G4 -MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit ctest --test-dir build-verify -L integration-verify -R 'Mipmap' ; echo rc=$? # non-zero: G5 -ctest --test-dir build-verify -R 'PoisonOmitted\.|VerifyCorrupted\.' --no-tests=error # the four control entries green (they assert the red) -# 40 traces (79 desktop cases) under verify - the second half of G3: -cp scratchpad/wf2/retrace_gate.py ~/w7/retrace_gate.py -MOBILEGL_PIPE_VERIFY=1 python3 ~/w7/retrace_gate.py --tree ~/w7/pipe --lib ~/w7/pipe/build-verify/libMobileGL.so --out ~/w7/retrace-out/p1-verify -j 4 -grep -L "MGPipe: verify armed" ~/w7/retrace-out/p1-verify/*/mobilegl.log # empty: every case armed -grep -l "Fatal{" ~/w7/retrace-out/p1-verify/*/mobilegl.log # empty -``` -A `Fatal{UnmigratedPipeInput, "F@V"}` here is a missing row in `FillPoints.def`'s class table: add it (never sticky), regenerate, rerun. A `Fatal{PipeVerifyDiffer, ..., where=read}` is a real push/pull semantic divergence: record it in `MEASUREMENTS.md` with the site and decide (plan owner) whether the pull value or the boundary value is the intended one; it is not silenced. - -D.4 CI: `gh workflow run test.yml --ref feat/disaggregated` and, once green, `gh workflow run test.yml --ref feat/disaggregated -f baseline_sha=087685d1` for `monolith-symbol-report`; record both run URLs. Expected: `pipe-gates`, `include-graph-check`, `test`, `integration`, `integration-verify`, `retrace` (77), `retrace-verify` (16), `monolith-symbol-report` green. - -D.5 Docs (integrator-owned, one commit; `scripts/check_doc_citations.py docs/Disaggregated/*.md` must stay clean): `README.md:3` status → P1 landed with the hash; `ROADMAP.md:17` ✅ with "277 处(274 行)+ 58 行", "Espryt 32 / Magma 56(63 字段)", "83 个填充点 / 69 个 verb"; `ROADMAP.md:18` unchanged (dirty-surface stays P2); `ARCHITECTURE.md:172,568` "P1 起成为门/P1 成门" → P2; `:344` counts; `:349` add `MOBILEGL_PIPE_POISON`'s definition and the `MOBILEGL_DEBUG` correction; `:553` note that `MGPipeImpl_*.cpp` land with the first installed table; `:583` `MOBILEGL_PIPE_VERIFY` → 已落地, add `MOBILEGL_PIPE_PUSH` (CMake) row and the three runtime knobs at `:588-597`; `MEASUREMENTS.md`: new §6 (the D10 table), §4 "61 字段" → 63 / "63 verify payload" → 69, the G1 symbol-report summary lines, the verify-lane wall times, and the P1 `.text` attribution (expected: "0 bytes; 58 lines converted, none code-bearing"). Then `[Merge]` to `dev` per the milestone flow (P1 is not one of the five full-CTS boundaries, `ROADMAP.md:34`, but the `dev` merge still triggers the full `gl44to46` run on both devices — schedule it, off the critical path). - ---- - -## E. Risks and mitigations - -| risk | mitigation | -|---|---| -| **A fill table that is too small turns the verify lane red for a reason that is not a bug.** The class masks in D7 are derived statically from `scout-pull-sites.md` §4.2; a helper reached through a path the reachability trace missed reads a field its class does not list. | This is exactly what the lane exists to find, and the fix is mechanical (add the row, regenerate). The rule in D7 — never sticky — keeps the fix honest. Budget one day of D.3 iteration for it; each Fatal names field and verb. | -| **A read outside any verb.** `InvalidateCompileEnv` at backend init (`BackendObject_DirectVulkan.cpp:389,787`) and `RecordError` from sampler sync run with no verb; texture-sync helpers (`Managers.cpp:3645-4737`) are reached only from verbs (callers verified: `DirectGLES.cpp:1325,1567,1632`, `Managers.cpp:5234`, all under `SyncNeccessaryTextures`/FBO sync); Magma's render-pass and view helpers are reached only from draw/clear/blit (`GetOrCreateRenderPass` callers `VulkanRenderer.cpp:6363,6833,6846,7196,7229,8491`); `Present` reads nothing. | The seven sticky fields cover the two genuine out-of-verb cases. If the lane finds another (`@` in the Fatal), the answer is a fill point at that boundary (an EGL virtual in `EGLImpl.cpp`), never a sticky flag. | -| **`.text` moves in the pull build.** The 13 guard/compare rewrites and the `.get()` block are like-for-like, but a compiler may schedule a `const void*` compare differently. | `symbol_report.py --threshold 0` after every merge (D.1); any delta is attributed line by line in the commit message and in `MEASUREMENTS.md`; the gate text allows an attributed delta, not an unexplained one. | -| **The verify lane is green because it never armed.** A `MOBILEGL_PIPE_VERIFY=1` against a library without the option is a no-op that looks green. | `run_trace_case.cmake` fails a verify run whose log lacks the arming line; `PipeVerifyArmingScenario` fails the itest lane the same way; `--no-tests=error` fails a lane whose registration was skipped; the retrace-verify job asserts the swapped `.so` exports `MGPipeVerifyInputs`. | -| **The entry compare is tautological in P1 and someone reads "zero divergence" as more than it is.** | D8 states it; `MEASUREMENTS.md` records which arm proved what; the compare-at-read arm and the poison are the P1 semantic content, and the CORRUPT control keeps the entry compare falsifiable until P2 gives it a real first arm. | -| **Verify cost.** 5–10× on 742 itest entries and 79 traces; the 1800 s trace cases already sit above ctest's 1500 s default. | `TIMEOUT 900` on the verify lanes; `--timeout 10800` on retrace-verify; `timeout-minutes` on every new job (the file has none today); per-push CI runs the 8-case verify subset, the full 79 runs at the P1 exit on WSL (D.3) and on `workflow_dispatch`; `-j 4` locally (each case unpacks a multi-GB trace). | -| **`std::abort()` inside a scenario kills the whole `MobileGLIntegrationTest` process.** | ctest runs one case per process (`gtest_discover_tests`), so a Fatal reds exactly one entry and leaves a core; the omission scenario forks first (`HeadlessGL.cpp:323-343`). | -| **The `GetFramebufferBindingSlotFast` cache bypasses the accessor** for five Espryt reads. | Known and recorded (D4); the entry compare still covers the field; P2's Espryt 0b deletes the cache. | -| **`Coverage.def` and the stale vendored inventory disagree** (a 62-accessor tree, a 61-row table, a dead row). | D12's `scan_live_accessors()` is a `pipe-gates` gate from `c5` on; the dead row is labelled; re-vendoring is a recorded P2 item. | -| **Two `PipeInputs` field lists drift from `RenderStateParameters` when P2 adds `FramebufferSrgb`/`DepthClamp` storage** (`ROADMAP.md:18`). | D8's struct-member coverage check makes a member without an `F(...)` entry a `pipe-gates` failure; `MGL_RESIDUAL_BLOCK_SIZE` and the value-header `static_assert` catch the size. | -| **MSVC / Android.** `fork()`-based tests and the itest lanes are POSIX; the push/verify options are desktop-only in P1. | Windows is not a correctness gate (`ROADMAP.md:7`); the new sources compile on MSVC (no POSIX in `PipeInputs`/`PipeFill`); the fork tests are `#if !_WIN32` with a visible skip; `apk.yml` is unaffected (options OFF). | -| **A/B/C/E land against an outdated contract.** | The contract is one commit, tagged; A must not change any signature in `PipeInputs.h`, `PipeFill.h` or `PipeInputsSwitch.h` after `p1/contract` without telling B/C/E (a rebase-time compile error is the backstop). | - ---- - -## B.7 (appendix) — the 83 fill-point statements at `087685d1` - -Insert `MGP_FILL();` immediately before each statement (D7 placement): - -- `GL_Drawing.cpp`: 530 Clear; 538 DrawElements; 547 MultiDrawElements; 556 MultiDrawElementsBaseVertex; 565 DrawArrays; 573 MultiDrawArrays; 582 DrawElementsBaseVertex; 591 MultiDrawElementsIndirect; 599 MultiDrawArraysIndirect; 608 MultiDrawElementsIndirectCount; 618 MultiDrawArraysIndirectCount; 628 DrawRangeElementsBaseVertex; 638 DrawRangeElements; 648 DrawElementsInstancedBaseVertexBaseInstance; 658 DrawElementsInstancedBaseVertex; 668 DrawElementsInstancedBaseInstance; 678 DrawElementsInstanced; 686 DrawElementsIndirect; 694 DrawArraysInstancedBaseInstance; 703 DrawArraysInstanced; 711 DrawArraysIndirect; 742 DispatchCompute (`dispatchCompute(numGroupsX, …)`); 794 DispatchComputeIndirect; 815 PatchParameteri; 885 MemoryBarrier; 907 MemoryBarrier (`TextureBarrier`); 919 MemoryBarrierByRegion; 1241 BeginTransformFeedback; 1323 EndTransformFeedback; 1331 FenceSync; 1332 ClientWaitSync; 1334 DeleteSync; 1352 PauseTransformFeedback; 1366 ResumeTransformFeedback; 1571 DeleteTransformFeedback; 1602 BindTransformFeedback — **36** -- `GL_Framebuffer.cpp`: 619 BlitFramebuffer; 632 BlitNamedFramebuffer; 643 ClearNamedFramebufferfv; 653 ClearNamedFramebufferfi; 663 ClearNamedFramebufferiv; 673 ClearNamedFramebufferuiv; 2732 ClearBufferfi; 2738 ClearBufferfv; 2744 ClearBufferuiv; 2750 ClearBufferiv; 2997 ReadPixels — **11** -- `GL_Texture.cpp`: 1079 ReadPixels; 1622 GenerateMipmap; 4027 CopyTexSubImage2D; 4043 CopyImageSubData; 4464 CopyTexImage2D; 5074 GetTexImage; 6456 GetTextureImage; 6660 BindImageTexture — **8** -- `GL_Getter.cpp`: 1176 GetIntegeri_v; 1356 GetGpuTimestampNs; 2266 GetGpuTimestampNs — **3** -- `GL_Program.cpp`: 3401 ShaderStorageBlockBinding — **1** -- `GL_Query.cpp`: 167, 274, 320, 444, 591, 915 DeleteBackendQuery; 181 EndTimeElapsedQuery; 261, 301 GetQueryResult64; 289 IsQueryResultAvailable; 425, 607 EndOcclusionQuery; 523 BeginXfbPrimitivesQuery; 530 BeginOcclusionQuery; 534 BeginTimeElapsedQuery; 582 EndXfbPrimitivesQuery; 646 QueryCounterTimestamp; 775 IsTimerQuerySupported — **18** -- `GL_Sync.cpp`: 59 FenceSync; 97 ClientWaitSync; 122 WaitSync; 142 DeleteSync; 177 GetSyncStatus; 229 DeleteSync — **6** - -Not fill points: the 8 null-check/capture lines the 91-line grep also matches (`GL_Drawing.cpp:1010,1031,1255,1329`, `GL_Query.cpp:218,471,545,768`, `GL_Texture.cpp:6455,6715`, `ProgramInterface.h:27`), `CompileEnv.cpp:135,137` (D1), and the EGL virtuals (`EGLImpl.cpp:178,267,284,347,448`). diff --git a/docs/Disaggregated/notes/recovered/wf4/P1-LANE-FINDINGS.md b/docs/Disaggregated/notes/recovered/wf4/P1-LANE-FINDINGS.md deleted file mode 100644 index 172e30eb..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/P1-LANE-FINDINGS.md +++ /dev/null @@ -1,65 +0,0 @@ -# P1 verify-lane findings on the integrated tree (`feat/disaggregated` @ 97b997d5, WSL `~/w7/pipe`) - -All four P1 packages are merged. The pull build is clean: `symbol_report --threshold 0` reports 0 added / 0 removed / 0 resized / 0 renamed, `grep -rc pGLContext MobileGL/MG_Backend` is empty everywhere, `ctest -L unit` 1482/1482, `ctest -L integration-gpu` 878 with one known `-j` flake that reruns green, `ctest -N` names 0 removed / 26 added, `gen_pipe.py --check` and `--self-test` clean. - -The push and verify builds are not clean. Three distinct problems, in the order they must be fixed. - -## F1 — missing fill rows (mechanical, `MobileGL/MG_Pipe/FillPoints.def`) - -The 79-case retrace under `MOBILEGL_PIPE_VERIFY=1` (`~/w7/retrace-out/p1-verify/`) aborts four cases with: - -``` -Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@GenerateMipmap"} x3 cases -Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@DispatchCompute"} x1 case -``` - -`IsCapabilityEnabled` is listed for `kDraw`, `kClear`, `kBlitOrCopy` and `kReadback` but not for `kTextureOp` (which owns `GenerateMipmap`) or `kDispatch`. Adding a row is the whole fix, but do not stop at these two: audit every class against the verb paths in `scratchpad/wf4/scout-pull-sites.md` §3b and add every row the lane can still miss, then prove it by re-running the lane and the retrace (they are the only real oracle). - -One row of the same kind was already fixed at `9087f133` (`kReadback` was missing `IsTransformFeedbackActive` / `IsTransformFeedbackPaused`, which the depth/stencil read emulation reads through `ScopedEmulationDrawState`, `DirectGLES.cpp:5224`). - -## F2 — the backend mutates frontend state inside a verb, so the pushed block goes stale mid-verb - -Two retrace cases and eight `integration-verify` entries abort with: - -``` -Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays", verb=3, where=read} -Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawElements", ..., where=read} -``` - -The failing entries are `DirectVulkan.Verify.UnboundImageDescriptorScenario.*` (6) and `DirectVulkan.Verify.SampledSetStalenessScenario.*` (2); the log line immediately before the divergence is `ResolveSamplerDescriptor: using fallback texture for unbound sampler ...`. - -Mechanism, verified in the tree: `GetSamplingResolutionGeneration` counts frontend mutations. It is bumped from exactly two places, both frontend objects: `SamplerObject::BumpVersion()` (`MG_State/GLState/SamplerState/SamplerObject.cpp:34`) and `TextureObjectBase::BumpShapeVersion()` (`MG_State/GLState/TextureState/TextureObject.cpp:37`). During a draw, Magma's own paths (fallback-texture resolution for an unbound sampler, materialising a queued clear) write into frontend texture/sampler objects, so the counter moves between the verb's fill and the backend's read. This is the known "backend writes into frontend objects" surface (about 40 call sites; see `scout-pull-sites.md` §2). - -This is a real finding, not a harness artefact, and it must not be silenced. Decide and implement one of: - -- **(preferred) push on mutation.** The frontend mutator that changes a pushed value refreshes that field in the pushed block (and restamps it for the current verb) when `MOBILEGL_PIPE_PUSH` is on. This keeps the comparator's invariant ("the pushed block equals the live context at every read") literally true, and it is the shape P2's tracker needs anyway. Layering is fine: `MG_State` already includes `MG_Pipe/MGPipeValueTypes.h`. -- **(fallback) an explicit volatile-in-verb class.** The field is compared at verb entry only, the compare-at-read is skipped for it, the reason is written next to the field list, and the exact backend call sites that mutate it are listed in `docs/Disaggregated/MEASUREMENTS.md` as P2/P7 work. - -Whichever you choose: enumerate every PipeInputs field a backend can invalidate through its own frontend writes (do not assume this one field is the only one; the lane found it because the fixture happened to hit it), handle them all the same way, add a unit test that reproduces the mid-verb mutation and would fail without the fix, and write the enumeration into the result file. - -## F3 — unit tests that drive backend helpers with no verb - -Eleven `ctest -L unit` entries fail in the push and verify builds and pass in the pull build: - -``` -DirectGLESSanity.BindsAMultisampleTextureDespiteTheDefaultMipmapFilter -DirectGLESSanity.BindingZeroClearsPreviousNativeTextureBinding -FramebufferTest.{DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff, - ClearIntoAWidenedDrawBufferKeepsAlphaWritableAndSubstitutesOne, - ApplicationAlphaMaskOffIsStillHonouredOnANativeDrawBuffer, - DualSourceBlendFactorsReachTheDriverWhenTheExtensionIsThere, - DualSourceBlendIsDeclinedRatherThanThrownWhenTheExtensionIsMissing, - DualSourceFactorsAreDeclinedEvenWithBlendingDisabled, - DualSourceFactorsWithBlendingDisabledStillReachACapableDriver} -DirectGLESTextureSync.* (1) -TextureTest.* (1) -``` - -These construct a `GLContext` by hand and call a backend helper directly (for example `DirectGLES::BindCurrentTextures()` at `MG_Test/SanityTest.cpp:366`), so no GL entry point runs and no `MGP_FILL` ever fires; the first accessor read hits the poison. The tests are right and the poison is right: what is missing is that a test which drives a backend helper directly must state which verb it is standing in. - -Fix in the test layer only (`MobileGL/MG_Test/**`): give those tests a small, explicit helper that fills the block for the verb class the helper under test belongs to (a scoped RAII "as if we were inside verb X" object is the natural shape), so the test declares the verb rather than the poison being weakened. Do not add a global escape hatch, do not make the poison tolerant in unit builds, and do not change any test name. The pull build must stay byte-identical. - -## F4 — corrections to the integrator's own commands (not code) - -- The lane-level poison control is not `-R 'Mipmap'`: the four dedicated control entries are `DirectGLES|DirectVulkan.VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported` and `...PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb` (all four pass). -- The retrace arming line is `MGPipe verify: armed, zero divergences, zero unmigrated reads` in the case log, not `MGPipe: verify armed`; 73 of 79 cases carry it, the other 6 are the F1/F2 aborts. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-contract.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-contract.md deleted file mode 100644 index 35380c4a..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-contract.md +++ /dev/null @@ -1,73 +0,0 @@ -# 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: - -1. `python3 scripts/gen_pipe.py` → wrote `PipeFilled.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. -2. `cmake --build build-linux -j 20` (reconfigured itself on the `CMakeLists.txt` change; `MOBILEGL_PIPE_PUSH=1` occurs 0 times in `build-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 `.cpp` files are not listed in the pull build — D5/D13: appended to `SOURCE_FILES` only under `MOBILEGL_PIPE_PUSH`; the new headers are included by nobody in the pull build; `Config.h`'s three members are behind `#if MOBILEGL_PIPE_PUSH`; the `MGPipe.h` include of `PipeFillPoints.inc` adds only `inline constexpr` tables that no pull TU odr-uses). -3. `cmake --build build-push -j 20` → rc=0, links (`MOBILEGL_PIPE_PUSH=1` on all 496 compile commands; `nm -C build-push/libMobileGL.so` exports `MGPipeFillForVerb`, `PipeInputs::IsLive`, `gPipeInputs`). Configure log: `MobileGL: PipeInputs push ON, appending the MGPipe fill sources`. -4. `cmake --build build-verify -j 20` → rc=0, links (`MOBILEGL_PIPE_PUSH=1` and `MOBILEGL_PIPE_VERIFY=1` on all 496; exports `MGPipeInputsFieldEqual`, `MGPipeApplyVerifyCorruption`). Configure log: `MobileGL: MOBILEGL_PIPE_VERIFY=ON forces MOBILEGL_PIPE_PUSH ON for this configure`. -5. `ctest -L unit --no-tests=error -j 12`: build-linux `100% tests passed, 0 tests failed out of 1466`; build-push `100% … out of 1466`; build-verify `100% … out of 1466`. -6. `(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.) -7. `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 pGLContext` on the two MGPipe files → `0 0`. -8. `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp` → 0 (fill points are c3). -9. Integration smoke in the push and verify libraries (`ctest -L integration-gpu -R 'ClearThenReadPixels|GenerateMipmap|Mipmap' -j 4`): build-push `100% tests passed, 0 failed out of 10`, build-verify `100% … out of 10`, zero `Fatal{` in either log — the inert filler and the unarmed poison do not disturb a run in which no backend site reads `gPipeInputs` yet. - -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) - -1. **`MGP_INPUT_CHECK` is live at c1** (under `MOBILEGL_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 hit `Fatal{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_READ` expands to nothing as specified (c4 arms it). -2. **Sticky stamping is in c1** (three lines in `MGPipeFillForVerb`: the seven sticky fields get `FilledGen = 1` on 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. -3. **`MGPipeInputsFieldEqual` and `MGPipeApplyVerifyCorruption` are 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 of `PipeInputs::VisitStorage` over `MGP_INPUT_STORAGE_LIST` (56 storage rows + 7 forwarded = 63, static_asserted) instead of two 63-way switches. `IsCapabilityEnabledIndexed`'s two arrays are one `IndexedCapabilities` struct so the list stays one row per field. -4. **`PipeFields.def` is untouched in c1.** C.1 step 1 says "`Coverage.def` + `PipeFields.def` edits" but step 5 (c5) owns the six G4 field lists / overloads / coverage check, and the task instruction for c1 does not list `PipeFields.def`. Deferred to c5. Consequence: `kMGPipeVerifiedPayloadCount` stays 63 at c1 and `RenderStateParameters` / `PixelStoreParameters` still go through the memcmp fallback inside `MGPipeInputsFieldEqual` (both arms are copied by assignment into zero-initialised globals, so padding is equal; c5 removes the fallback anyway). -5. **`PipeInputs::IsLive()` forwards to the live context** (defined in `PipeFill.cpp`, returns `pGLContext != nullptr`) instead of returning a fill-time `m_live` flag; `m_live` is still recorded at fill for the filler's own use. Reason: D5 lists `IsLive` among `PipeFill.cpp`'s contents and D9 says `MGB_CTX_LIVE` "is always true once a context exists" under push, which a fill-time flag cannot promise before the first verb (e.g. `InvalidateCompileEnv` at backend init, `BackendObject_DirectVulkan.cpp:389,787`). `ContextIdentity()` is the fill-time address as specified. -6. **`GetBufferBindingPointCount` is not `constexpr`** in `PipeInputs` (GLContext's is): it is an out-of-line forwarder per D4's F class. No backend uses it in a constant expression. -7. **`MGPipeFieldMask::Words[2]`** as the brief says, generated as `max(2, ceil(fields/64))` so a 129th field grows the mask instead of silently truncating (`static_assert(kMGPipeInputFieldCount <= 2 * 64)` in the `.inc`). -8. **Two extra `gen_pipe.py` checks beyond the brief's list**: verb rows must be in `GLFunctionsTable` declaration order (not just the same set), so `MGPipeVerb`'s numbering is the table's by construction; a class listed twice in `MGP_FILL_CLASS_LIST` is refused. `--self-test`, `scan_live_accessors()` and `check_field_lists_cover_struct_members()` are c5, as the brief's step 5 says. -9. **Tree-vs-brief line facts**: `CMakeLists.txt` "after :451" — the disaggregated fallback's `endif()` is at `:452`; the block was inserted after it (before the `if (MOBILEGL_BUILD_DISAGGREGATED)` sources block at `:454`). The `MOBILEGL_COMPILE_DEF` appends went after the `endif()` at `:525` as stated. The brief's ctest-name regex issue is item 6 above. -10. **Accessor namespaces**: `BufferTarget` and `FramebufferTarget` live in `namespace MobileGL` (not `MG_State::GLState`, where the brief's text implies all frontend types sit); the header's `using` aliases spell them accordingly. Everything else (`VertexArrayObject`, `ProgramObject`, `ITextureObject`, `BufferObject`, `FramebufferObject`, `TextureUnit`, `ImageTextureBinding`, `CurrentVertexAttributeValue`) is `MG_State::GLState::`; value types are `MobileGL::`. -11. `IsCapabilityEnabled` with a cap ≥ `CapabilityInputCount` returns `false` (defensive bound; GLContext would index its own table); indexed getters mirror `RenderState`'s bounds handling (`MOBILEGL_ASSERT` + element 0 / early return; `GetCurrentVertexAttribute` mirrors `Core.cpp:246-253` with `MGLOG_E_ONCE` + a static default). - -## Unfinished / next for A - -- c2: per-class copies and stamps in `MGPipeFillForVerb` (mask = `kMGPipeClassFieldMask[kMGPipeVerbClass[verb]]`), `MOBILEGL_PIPE_POISON_OMIT` parsing. -- c3: the 83 `MGP_FILL(X);` statements (§B.7). -- c4: `SnapshotFromGLContext`, entry compare, `MGPipeVerifyReadHook` behind `MGP_INPUT_VERIFY_READ`, arming/knob log lines, `PipeVerifyFatal`. -- c5: `PipeFields.def` field lists, `MGPipeFieldEqual` overloads, `check_field_lists_cover_struct_members`, `scan_live_accessors`, `--self-test`. -- c6: the unit tests listed in C.1. -- Full `ctest -L integration-gpu` in build-push (after c3). diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v1.md deleted file mode 100644 index c2ce46e7..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v1.md +++ /dev/null @@ -1,103 +0,0 @@ -# Adversarial review — p1-core v1 (tree `~/w7/p1-core`, branch `p1/core`, HEAD `440d3c52`) - -Verdict: **NOT approved — 1 major, 8 minors.** The package is otherwise complete against C.1 and the D-decisions; every claim in `core-v1.md` that I could re-run reproduced. The one major is a defect in the delivered G5 negative-control unit tests, not in the filler/poison/comparator code. - -Tree left exactly as found (`git status` clean apart from the three untracked build dirs; no file edited, nothing committed). All logs under `~/w7/p1-core-review/`. - -## 1. What I re-ran (all on `440d3c52`; builds were `ninja: no work to do` in all three dirs) - -| check | command | observed | -|---|---|---| -| G1 symbols | `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 --json` | `.text 10792579 -> 10792579 (+0)`, `Total +0`, `27799 -> 27799: 0 added, 0 removed, 0 resized, 0 renamed` | -| G1 bytes | `cmp -l before after \| wc -l`; `readelf -SW` diff; `strings` diff | 34 bytes; section table identical; the only differing strings are `GIT@5d99ee4 -> GIT@83b1656` and glslang's `Timestamp: 2026-09-05T23:14:30 -> 2026-09-06T02:05:06` (see minor 6 for the stamp value) | -| G2 (A's half) | `grep -rc pGLContext MobileGL/MG_Backend \| sort \| diff - ~/w7/p1-before-pglcontext.txt` | only `MG_Backend/MGPipe/PipeInputs.{cpp,h}:0` appear; every catalogued file's count unchanged — A converts no site, touches no B/C file | -| `pGLContext` in A's backend files | `grep -c pGLContext MobileGL/MG_Backend/MGPipe/*` | `0 0` | -| fill points | `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp` | Drawing 36, Framebuffer 11, Texture 8, Getter 3, Program 1, Query 18, Sync 6 = **83**; no `MGP_FILL` outside `MG_Impl/GLImpl` | -| generator | `python3 scripts/gen_pipe.py --check` / `--self-test` | rc 0 / rc 0; `63 PipeInputs fields (7 sticky), 69 verbs, 9 classes`, `generated files are up to date`; six controls trip, positive control OK | -| D7 table vs `FillPoints.def` | `d7check.py` (mechanical diff of the brief's verb→class and class→field tables against the .def) | 69/69 verbs identical; 139/139 (class, field) rows identical | -| D8 field lists vs the structs (independent oracle) | clang `-ast-dump=json -ast-dump-filter=` on `PipeFill.cpp`'s compile command, `FieldDecl` names vs `PipeFields.def` (`ast.py`) | RenderStateParameters 65/65, PixelStoreParameters 8/8, PerBufferBlendState 7/7, StencilFaceState 7/7, DynamicBackendParameters 85/85, MGHostSpan 5 members of which `Pad0` is the excluded padding → 4/4. The regex-based `check_field_lists_cover_struct_members()` agrees with clang on every struct | -| accessor signatures | `Core.h:66-506` vs `PipeInputs.h` | all 63 identical in name, parameters, constness and return type (incl. the `const&` returns for `GetRenderStateParameters`, `GetBlendColor`, `GetViewportIndexed`, …; no by-value copy of the 1168-byte struct was introduced). Only difference: `GetBufferBindingPointCount` is `constexpr` on `GLContext` and a plain forwarder here — the three backend uses (`DirectGLES.cpp:474`, `UniformManager.cpp:1189,2013`) are runtime, so nothing breaks | -| fill-probe side effects | `BufferState.cpp:90-98`, `TextureState.cpp:157-173`, `Core.cpp:179,594` | `GetBindingPoint(t,0)`, `GetUnitObject(0)`, `GetImageTextureBinding(0)`, `GetBoundVertexArray()` are pure lookups; `TouchBindPoint` is only reached from `Core.h:84` (the setter). `GetProgramForDraw()` joins link+SPIR-V — same verb as today's first read | -| unit, 3 configs | `ctest -L unit --no-tests=error -j 12` in build-linux / build-push / build-verify | `100% tests passed, 0 tests failed out of 1475` × 3 | -| `ctest -N` names | `comm` against `~/w7/p1-before-ctest-names.txt` | 2343 names, **0 removed**, 9 added; identical name sets in push and verify | -| skip visibility | `ctest -R 'PipeInputsTest' -V` | build-linux: all 5 `***Skipped` ("push not compiled in"); build-push: 4 skipped (poison/verify), `ReadingAFilledFieldCompletes` passes; build-verify: all 5 pass | -| nm -D | `nm -D --defined-only \| grep -c MGPipeVerifyInputs` | push 0, verify 1 | -| integration-gpu, push | `ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4` | `100% tests passed, 0 tests failed out of 868`, `grep -c 'Fatal{'` = 0 | -| G4 lane smoke, verify | `MOBILEGL_PIPE_VERIFY=1 ctest -R 'DirectGLES\..*ClearThenReadPixels'` | 5/5 green; log has 1 × `MGPipe: verify armed - 63 fields, 69 verbs, fatal=1`, 0 `Fatal{` | -| G4 negative control A | same + `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` | rc 8, 0/5; `verify corruption armed - GetRenderStateParameters`; `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear", verb=1, where=entry}` | -| FATAL=0 | + `MOBILEGL_PIPE_VERIFY_FATAL=0` | 5/5 green, 0 `Fatal{PipeVerifyDiffer` aborts, `verify summary - 3 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0` | -| bad CORRUPT name | `MOBILEGL_PIPE_VERIFY_CORRUPT=NoSuchField` | rc 8, `Fatal{PipeVerifyBadKnob, "MOBILEGL_PIPE_VERIFY_CORRUPT=NoSuchField": no such field in kMGPipeInputFieldNames}` | -| POISON_OMIT env path, verify | `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit ctest -R 'DirectGLES\..*ClearThenReadPixels'` | green (no backend reads `gPipeInputs` on this tree), log has `poison omission armed - GetActiveTextureUnit@GenerateMipmap`, 0 `Fatal{` | -| POISON_OMIT bad verb | `MOBILEGL_PIPE_POISON_OMIT=NoSuchVerb:GetActiveTextureUnit` | rc 8, `Fatal{PipeVerifyBadKnob, "MOBILEGL_PIPE_POISON_OMIT=NoSuchVerb:GetActiveTextureUnit": no such verb in kMGPipeVerbNames}` | -| POISON_OMIT in a POISON-less push build | same in build-push | green, `MGPipe: poison omission GetActiveTextureUnit@GenerateMipmap requested but the poison is not compiled in (MOBILEGL_PIPE_POISON=0)` | -| `MOBILEGL_PIPE_VERIFY=1` in a push build | build-push | green, one `MGPipe: MOBILEGL_PIPE_VERIFY=1 requested but the comparator is not compiled in` | -| G5 unit, serial | `ctest --test-dir build-verify -R PipeInputsTest -j 1` | 5/5 | -| **G5 unit, parallel** | `ctest --test-dir build-verify -R PipeInputsTest -j 8` × 40 | **40/40 runs red** (major 1) | -| commit hygiene | `git log --format=%B` | 6 commits, `[Type] (Scope): …` subject + blank + `- ` bullets; 0 `Co-Authored-By`; nothing pushed | -| ownership | `git diff --stat feat/disaggregated..HEAD` (26 files) | every path is in A's C.5 rows; `DirectGLES/**`, `DirectVulkan/**`, `PipeCalls.def`, fixtures, `MG_IntegrationTest/**`, `test.yml`, docs untouched | -| logging discipline | grep `MGLOG_D`/stdio in `PipeFill.cpp`, `PipeInputs.{h,cpp}` | none | -| line endings | `git diff --name-only … \| xargs file \| grep -i crlf` | none (all LF) | - -## 2. Major - -### M1 — The G5 negative-control unit tests share one fixed log file and fail 100% under `ctest -j` - -`MobileGL/MG_Test/Pipe/PipeInputsTest.cpp:255-270` (`main()`): every `PipeInputsTest` process computes the same path `temp_directory_path()/mobilegl-pipeinputs-test.log`, **unlinks it** (`fs::remove(path, ec)`, line 261) and points `MOBILEGL_LOG_FILE_PATH` at it. `gtest_discover_tests` registers the five cases as five separate processes, so under a parallel ctest all five start within milliseconds, each deleting the file the others are writing to and reading from (`ReadLog()` reopens by path, lines 49-54, 144/162, 175/191). - -Reproduction (verify build, this tree): - -``` -ctest --test-dir build-verify -R 'PipeInputsTest' -j 1 # 100% tests passed, 0 tests failed out of 5 -ctest --test-dir build-verify -R 'PipeInputsTest' -j 8 # red; 40 of 40 iterations red (~/w7/p1-core-review/stress2-*.log) -``` - -Observed failure text (iteration 1, `--output-on-failure`): - -``` -../MobileGL/MG_Test/Pipe/PipeInputsTest.cpp:192: Failure -Expected equality of these values: - log.find("Fatal{") - Which is: 49 - std::string::npos - Which is: 18446744073709551615 -[02:20:47] [Linux PipeInputsTest/FATAL]: MGPipe: Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"} -[ FAILED ] PipeInputsTest.ReadingAFilledFieldCompletes (0 ms) -``` - -i.e. `ReadingAFilledFieldCompletes` reads the **sibling process's** Fatal line out of the shared file and goes red; in the first stress loop (`stress-*.log`) the other direction showed: `ReadingAnOmittedFieldAbortsNamingTheVerb` red 25/25 because a sibling's `main()` unlinked the file between the child's write and the parent's read, so its own line was gone. - -Why major: these two cases are the package's G5 proof (`core-v1.md` "G5 unit"), and they go red for a reason that is not the poison — the opposite of `ROADMAP.md:7` (a gate goes red for its own reason) — whenever anyone runs the unit label with `-j` (the integrator's habit in D.1/D.3 is `-j 4`; my full `-L unit -j 12` run happened to pass only because the five cases were spread among 1470 others). The CI `test` job (`test.yml:192,194`) runs `ctest -L unit` serially today, which is why it has not shown up there; that is luck, not a property of the test. Fix is local to the test: a per-process file (e.g. `mobilegl-pipeinputs-test-.log`, removed at exit) — the forked child inherits the path, so nothing else changes. - -## 3. Minors - -1. **Undeclared 10th placement deviation — `GL_Sync.cpp:235` (`DeleteSync` in the orphan sweep).** `MGP_FILL(DeleteSync);` sits inside the loop body but **before** `if (backendDeleteSync && syncObject->backendHandle)`, so a backend with a null `DeleteSync` entry, or a sync without a backend handle, bumps the serial for every orphan. D7 says a null entry never bumps, and the sibling loop at `GL_Query.cpp:937` (`DeleteBackendQuery`) was placed inside the guard. Harmless (kQuery fills one field; nothing reads between two fills) — the same argument as the nine sites the result file declares — but it is not in the declared list (deviation 1 names 9 sites). Either move it inside the guard or add it to the record. - -2. **The compare-at-read arm has zero execution coverage in this package.** `MGPipeVerifyReadHook` (`PipeFill.cpp:409-426`) is the arm the result file and D8 call "the arm that is real in P1", yet no unit test calls an accessor with `Features.PipeVerify` set after mutating the live context, and on this tree no lane can reach it (no backend spells `MGB_CTX->` yet, so no accessor is ever entered). It is exercised for the first time only in D.3 on the integrated tree, where a hook that silently never fires would look like "zero divergence". The code reads correctly (`&self == &gPipeInputs`, sticky skip, `InHook` guard, `CopyField` into `g_readScratch`, `MGPipeInputsFieldEqual`, `ReportDivergence(field, "read")`), but the brief's own mitigation for "green because it never ran" is a falsifier, and the entry compare got one (CORRUPT) while this arm got none. Cheap unit test in the same fork shape: `MG_Config::Features.PipeVerify = true; MGPipeFillForVerb(DrawArrays); pGLContext->SetLineWidth(2.f); (void)gPipeInputs.GetLineWidth();` → expect `SIGABRT` and `Fatal{PipeVerifyDiffer, "GetLineWidth@DrawArrays", verb=, where=read}`. (Not required by C.1's test list, hence minor.) - -3. **`OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale` does not prove "exactly".** `PipeInputsTest.cpp:106-132` asserts one sibling (`GetTextureUnitObject`) and one sticky field; "exactly that field" would be a loop over `kMGPipeClassFieldMask[kTextureOp]` asserting every other bit fresh (the shape `EveryVerbFillsItsClassAndNothingElse` already uses at `:238-247`). Two lines; makes the test name true. - -4. **The two env-knob parsers are not covered by any registered test.** `ParsePoisonOmissionKnob` (`PipeFill.cpp:300-316`) and `ArmVerify`'s CORRUPT parse (`:351-371`) are only reachable through `MG_Config::Features`, which the unit binary never loads (I confirmed: `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit ctest -R ReadingAFilledFieldCompletes` in build-verify does not arm — the itest path does). I exercised both parsers manually through `DirectGLES.*ClearThenReadPixels` (table above: armed lines, `PipeVerifyBadKnob` on a bad field and a bad verb, the W_ONCE in a POISON-less push build) and they behave; the record is that this coverage lives in E's lanes on the integrated tree, not in A's tests. - -5. **`IsCapabilityEnabledIndexed` semantic asymmetry to record.** Per D6 the push accessor aborts for a cap other than `Blend`/`ScissorTest` (`PipeInputs.h:448-460`), while the pull arm returns `false` with an `MGLOG_I` (`RenderState.cpp:471-488`: "a query must never be able to terminate the process"). No backend asks another cap today (`VulkanRenderer.cpp:5437` asks `Blend`), so it cannot trip in P1, but it is the one accessor where push and pull disagree on a *reachable* input by design; worth a line in `MEASUREMENTS.md` (integrator) so the next person who adds `glIsEnabledi`-driven backend reads knows the push arm is fatal there. - -6. **`build-linux/libMobileGL.so` carries `GIT@83b1656`, not the `GIT@087685d` the result file cites.** The stamp is taken at configure time; the library in the tree was last linked at c5 (`83b16561`), and c6 (`440d3c52`) touches only `MG_Test/Pipe/**`, so the G1 comparison is of the right bytes (I confirmed `ninja: no work to do` and the section table identical). Purely a bookkeeping mismatch between `core-v1.md` and the artifact; the integrator's D.1 re-run on `~/w7/pipe` supersedes it. - -7. **Generated-file "7 files change" (C.1) vs 3 — correctly recorded by the implementer**, and I confirm the reason: `Coverage.def`'s two new accessor rows change `PipeFilled.inc` (63 + sticky) but not `PipeCoverage.inc` (which iterates inventory rows only), and `PipeVerify.inc` changes at c5. `--check` is clean, so nothing is stale. - -8. **`kMGPipeForwardedFieldCount = 7` is duplicated by hand** (`PipeInputs.h:133`) next to the generated `kMGPipeInputStickyFieldCount = 7` (`PipeFilled.inc:230`); the two are tied only by `StickyFieldsAreExactlyTheSeven`. A `static_assert(kMGPipeForwardedFieldCount == kMGPipeInputStickyFieldCount)` in `PipeInputs.h` would make the header refuse an eighth sticky row instead of the test. - -## 4. Where the tree and the brief disagree (verified; the implementer's list is right) - -- `GL_Drawing.cpp:906/907` `MemoryBarrier (TextureBarrier)`: the call is on 906, the brief's 907 is the closing brace — fill placed at (now) 932, before the call. ✓ -- `RenderStateParameters` has **65** direct data members (clang FieldDecl count; brief D8 says 67). `DynamicBackendParameters` **85**. ✓ -- `CapabilityInput`: storage is sized from the enum; the brief's "37" is not load-bearing anywhere in the code. (Not independently recounted.) -- The result file's `ctest -N` regex note (`'^\s+Test\s+#[0-9]+:'`) is right: ctest right-aligns the number; the brief's `'^\s+Test #'` would drop the padded rows. My name diff used the padded form and matched the baseline count (2343 = 2334 + 9). - -## 5. Notes for the integrator (not A's findings, surfaced while reviewing) - -- **The brief's CI negative-control step (C.4 (b)) as spelled is vacuous on this tree:** `ctest -L integration-verify -R 'DirectGLES\.Verify\..*Mipmap'` — no integration test has "Mipmap" in its name (`ctest -N | grep -i mipmap` finds 7 names, all `TextureTest.*`/`DirectGLESSanity.*` unit-label cases; `grep -rli generatemipmap MobileGL/MG_IntegrationTest/` is empty). With `--no-tests=error` a no-match run exits non-zero, so the inverted "must fail" check would pass without any process ever loading the library. E's `PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb` does not contain "Mipmap" either. Either the scenario's name or the regex needs to change, and the step should additionally grep the lane log for `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}` so it cannot be satisfied by "no tests found". Same applies to D.3's `-R 'Mipmap'` line. -- The compare-at-read hook (minor 2) is the thing to watch in D.3: if the 79 traces and 742 entries report zero `where=read` divergences, make sure at least one accessor was actually entered under verify (e.g. count `MGPipe: verify read of` lines with a deliberately mutated context, or land minor 2's unit test first). - -## 6. Verdict - -Not approved: fix M1 (per-process log path in `PipeInputsTest::main`, and re-run `ctest --test-dir build-verify -R PipeInputsTest -j 8` a few dozen times green), then re-review. Minors 1-3 and 8 are one-line-each and worth taking in the same pass; 4-7 are records. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v2.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v2.md deleted file mode 100644 index 0efda024..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v2.md +++ /dev/null @@ -1,95 +0,0 @@ -# Adversarial review — p1-core v2 (tree `~/w7/p1-core`, branch `p1/core`, HEAD `d9f4698d`) - -Verdict: **NOT approved — 1 major, 5 minors.** Every claim in `core-v2.md` that can be re-run reproduced on this HEAD, all eight v1 items are fixed or honestly recorded, and the four rework commits are correct for what they set out to do. The major is not in the rework; it is a gap in the poison mechanism itself that neither round's tests reach, and it contradicts a sentence of D6 verbatim. It is a one-line fix in a file this package owns. - -Tree left exactly as found: `git status` shows only the three untracked build directories; no file edited, nothing committed, nothing pushed. Reviewer logs under `~/w7/p1-core-review2/`; scratch inputs under `scratchpad/wf4/core-review2/` (`s1.sh`, `s2.sh`, `s3.sh`, `prefill.cpp`, `d7check.py`). - -## 1. What I re-ran (all on `d9f4698d`; all three build dirs were `ninja: no work to do`) - -| check | command | observed | -|---|---|---| -| G1 symbols | `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0` | `.text 10792579 -> 10792579 (+0, +0.000%)`; `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed, 27060 unchanged` | -| G1 bytes | `cmp -l … \| wc -l`; `readelf -SW` diff | 34 bytes; section table identical; library stamp `GIT@83b1656` (configure-time, as the result file records) | -| G2 (A's half) | `grep -rc pGLContext MobileGL/MG_Backend \| sort \| diff - ~/w7/p1-before-pglcontext.txt` | only `MG_Backend/MGPipe/PipeInputs.{cpp,h}:0` appear; every catalogued count unchanged — A converts no site, touches no B/C file | -| `grep -c pGLContext MobileGL/MG_Backend/MGPipe/*` | | `0 0` | -| fill points | `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp` | Drawing 36, Framebuffer 11, Getter 3, Program 1, Query 18, Sync 6, Texture 8 = **83**; none outside `MG_Impl/GLImpl` | -| fill placement | read of every hunk in `git diff feat/disaggregated..HEAD -- MobileGL/MG_Impl/GLImpl` | every statement is immediately before its table call and after the early returns it is behind; the frontend mutation that precedes the call (`BindImageTexture`'s `Bind`/`NoteTextureUnitTouched`, `SetPatchVertices`, `BeginTransformFeedback`, `SetTransformFeedbackPaused`, `BindTransformFeedbackObject`, `ScopedNeutralPackState`) is before the fill in every case; the nine guarded-expression sites are the nine declared; `GL_Sync.cpp:236` now inside its guard (v1 minor 1 fixed) | -| generator | `python3 scripts/gen_pipe.py --check` / `--self-test` | rc 0 / rc 0; `63 PipeInputs fields (7 sticky), 69 verbs, 9 classes`, `generated files are up to date`; 6 controls trip, positive OK | -| **generator gates against real drift** (scratch copy `/tmp/gp-review`, never the tree) | 11 mutations, `--check` each | m1 `MGB_CTX->NotAnAccessor()` in `Utils.cpp` → rc 1 `Coverage.def: accessor(s) read by a backend with no row: NotAnAccessor (…/Utils.cpp)`; m2 the same inside a `//` comment → rc 0 (masked); m3 `MGB_CTX->GetBoundTransformFeedbackName()` → the "no backend reads" line disappears (the `MGB_CTX->` spelling IS collected — first time it has met one); m4 `pGLContext->NotAnAccessor2()` → rc 1; m5 `GenerateMipmap` verb row deleted → rc 1 `member(s) without a verb row: GenerateMipmap`; m6 verb class changed → rc 1 `OUT OF DATE: …PipeFillPoints.inc`; m7 `Int ReviewerExtraMember` added to `RenderStateParameters` → rc 1 `member(s) with no F(...)`; m8 field row swapped → rc 1 (stale inc); m9 generated file touched → rc 1; m10 duplicate `(kDraw, GetLineWidth)` → rc 1; m11 `X(kDraw, NotAField)` → rc 1 `not an accessor in Coverage.def` | -| D7 tables vs `FillPoints.def` | `d7check.py` (brief tables parsed mechanically) | 69/69 verb→class identical; 139/139 (class, field) rows identical, per class 47/14/18/18/7/15/8/11/1 | -| unit, 3 configs | `ctest -L unit --no-tests=error -j 12` in build-linux / build-push / build-verify | `100% tests passed, 0 tests failed out of 1481` × 3 | -| `ctest -N` names | padded regex, `comm` against `~/w7/p1-before-ctest-names.txt` | 2349 names in each dir; **0 removed**, 15 added (4 `PipeCatalogue.*` + 11 `PipeInputsTest.*`); linux/push/verify name sets identical | -| skip visibility | `ctest -R PipeInputsTest -V` | build-linux 11 `***Skipped`; build-push 9 skipped + `ReadingAFilledFieldCompletes`, `BadPoisonOmitKnobIsFatalNamingTheKnob` pass; build-verify 11/11 pass | -| **M1(v1) repro, fixed** | `ctest --test-dir build-verify -R PipeInputsTest -j 8` × 40 | **0/40 red**; build-push × 10 → 0/10; `ls /tmp/mobilegl-pipeinputs-test* \| wc -l` = 0 afterwards | -| nm -D | `nm -D --defined-only \| grep -c MGPipeVerifyInputs` | push 0, verify 1 | -| integration-gpu, push | `ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4` | `100% tests passed, 0 tests failed out of 868`; `grep -c 'Fatal{'` = 0 | -| G4 lane smoke, verify | `MOBILEGL_PIPE_VERIFY=1 MOBILEGL_LOG_FILE_PATH=… ctest --test-dir build-verify -R 'DirectGLES\..*ClearThenReadPixels'` | 5/5 green; 1 × `MGPipe: verify armed`, 0 `Fatal{`, 0 `verify read of` (no backend reads `gPipeInputs` on this tree — expected) | -| G4 negative control A | + `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` | rc 8, 0/5; `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear", verb=1, where=entry}` | -| FATAL=0 | + `MOBILEGL_PIPE_VERIFY_FATAL=0` | 5/5 green; 1 × `verify summary - … survived MOBILEGL_PIPE_VERIFY_FATAL=0` | -| POISON_OMIT env path | `MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit` | green (no reader on this tree), 1 × `poison omission armed` | -| accessor edge semantics | `Core.cpp:98-110,246-253,309-319,806,938-948,965-1041,1087,1180`; `RenderState.cpp:18-26,101-107,471-478,541-593,666,694-696,770-776`; `BufferState.cpp:43-50,90-97`; `TextureState.cpp:157-173` vs `PipeInputs.h:199-556` | bounds handling matches GLContext's for every indexed accessor (same `MOBILEGL_ASSERT`+fallback shapes, `GetColorMaskIndexed` unchecked on both sides, stencil `Back→1 else 0` = `GetStencilFaceIndex`); `GetRenderStateParameters` returns `const&` on both sides — no 1168-byte copy on a read path | -| O-class target domain | `grep -o 'GetBufferBindingSlot([^)]*)' MG_Backend`; `SyncBoundBuffer` callers `DirectGLES.cpp:664,691`; `GetBufferBindingPoint` targets | slots read: DrawIndirect ×16, PixelPack ×7, Parameter ×4, DispatchIndirect ×1 — all in `GlobalBufferTargets`; points read: Uniform/TransformFeedback/AtomicCounter/ShaderStorage = `BufferBindPointTargets`; the variable-target loop at `DirectGLES.cpp:369-370` is bounded by a touched count that is 0 for any other target. No read the fill leaves null is reachable today | -| commit hygiene | `git log --format=%B 440d3c52..HEAD` | 4 commits, `[Type] (Scope): …` + blank + `- ` bullets, 0 `Co-Authored-By`; nothing pushed; all LF | -| ownership | `git diff --stat feat/disaggregated..HEAD` (26 files) | every path in A's C.5 rows; the rework touched 4 files, all A's | -| logging discipline | grep `MGLOG_D`/stdio in `PipeFill.cpp`, `PipeInputs.{h,cpp}` | none | - -## 2. Major - -### M1 — The poison (and the compare-at-read hook) cannot trip on any read made before the first fill: `FilledGen == 0` is "fresh" while `CurrentVerbSerial == 0` - -D6, verbatim: `++m_filled.CurrentVerbSerial` "(starts at 1 so `FilledGen == 0` means never filled)", and the Fatal "before the first verb the name is ``" (`PipeInputs.h:29-30` repeats the promise: `"" before the first verb`). The freshness predicate that every `MGP_INPUT_CHECK` (`PipeInputs.h:44-49`) evaluates is - -``` -MobileGL/MG_Pipe/generated/PipeFilled.inc:313-317 (emitted by scripts/gen_pipe.py gen_filled) -inline Bool MGPipeInputFieldIsFresh(const MGPipeFilledState& state, MGPipeInputField field) { - return kMGPipeInputFieldSticky[index] ? state.FilledGen[index] != 0 - : state.FilledGen[index] == state.CurrentVerbSerial; -} -``` - -Before the first `MGPipeFillForVerb` both sides are 0 (`gPipeInputs` is value-initialised, `PipeInputs.h:612,678`), so `0 == 0` is **fresh**: every V-class accessor and both `SharedPtr` O-class accessors return their default-constructed storage (zeros, empty `SharedPtr`) with no Fatal. The serial only "starts at 1" *after* the first fill (`PipeFill.cpp:517-519`), which makes 0 distinguishable from every filled generation but does not make it stale. The second net has the same window: `ArmVerify()` runs only inside `MGPipeFillForVerb` (`PipeFill.cpp:505-507,361-366`), so `MGPipeVerifyReadHook` sees `g_verify.Enabled == false` (`PipeFill.cpp:421`) until the first fill even when `Features.PipeVerify` is already set. Only the four raw-pointer O-class accessors (`GetBufferBindingSlot`, `GetBufferBindingPoint`, `GetFramebufferBindingSlot`, `GetImageTextureBinding`/`GetTextureUnitObject`) trip before the first fill, through their null-base checks (`PipeInputs.h:489,499,512,521,552`) — 55 of 63 fields are silently served wrong values. - -Reproduction (a 30-line program compiled and linked with `PipeInputsTest`'s own ninja commands against `build-verify`, nothing in the tree touched; `scratchpad/wf4/core-review2/prefill.cpp`, `s3.sh` section (a)): - -``` -$ MOBILEGL_LOG_FILE_PATH=~/w7/p1-core-review2/prefill-plain.log ~/w7/p1-core-review2/prefill -before-first-fill: serial=0 gen[GetLineWidth]=0 fresh=1 live=1 -read completed: stored=0 live=7 # gPipeInputs.GetLineWidth() after pGLContext->SetLineWidth(7.f), no fill -GetRenderStateParameters completed: LineWidth=1 -GetProgramForDraw completed: (nil) -rc=0 # expected per D6: SIGABRT, Fatal{UnmigratedPipeInput, "GetLineWidth@"} -$ … prefill x # same with MG_Config::Features.PipeVerify = true before the reads -before-first-fill: serial=0 gen[GetLineWidth]=0 fresh=1 live=1 -read completed: stored=0 live=7 -rc=0 # no "verify armed", no "verify read of", no Fatal in the log -``` - -Why major: this is the "poison that can never trip" shape, on the exact window the brief's own risk table names (E: "A read outside any verb … If the lane finds another (`@` in the Fatal), the answer is a fill point"). With this predicate the lane cannot find one for a V-class field: a backend read that lands before the first verb — an init-time capability probe, the first link, anything reached from `eglMakeCurrent` — is served zeros by the push arm, is not compared by the read hook, and reports nothing. `PipeCatalogue.PipeInputFieldsStartUnfilled` (`PipeCatalogueTest.cpp:225-228`, pre-P1) sets `state.CurrentVerbSerial = 1` by hand before asserting "unfilled", i.e. the P0.5 test already steps around serial 0; none of the 11 `PipeInputsTest` cases reads before a fill. Honest scope: **latent on this tree** — the scout catalogue (`scout-pull-sites.md:951`) finds exactly two out-of-verb pulls, both `InvalidateCompileEnv` (sticky, forwarded, unchecked), so D.3 will not hit it today. It is still a deviation from D6 that nobody declared, in a mechanism whose whole value is catching the case that is not catalogued yet. - -Fix (A's files): emit `state.FilledGen[index] != 0 && state.FilledGen[index] == state.CurrentVerbSerial` for the non-sticky arm in `gen_filled` (`gen_pipe.py`, the `MGPipeInputFieldIsFresh` text) and regenerate; add the serial-0 assertion to `PipeInputFieldsStartUnfilled`; add one fork case (`ReadingBeforeAnyFillAbortsNamingNoVerb`: live context, no fill, `gPipeInputs.GetLineWidth()` → SIGABRT + `Fatal{UnmigratedPipeInput, "GetLineWidth@"}`). With the poison fixed the read hook's arming-at-first-fill is harmless in a verify build (a pre-fill read is Fatal before the hook matters); if a plain push build without POISON is also meant to be covered, arm in the hook when `!g_verify.Parsed`. - -## 3. Minors - -1. **The seven F-class forwarders carry no `MGP_INPUT_CHECK`** (`PipeInputs.h:563-571`, `PipeFill.cpp:464-500`), while D4 says "Every accessor body is: `MGP_INPUT_CHECK(...)` → `MGP_INPUT_VERIFY_READ(...)`" and the header comment narrows it to "every non-forwarded accessor" (`PipeInputs.h:40,155`). This is the *right* behaviour — the two init-time `InvalidateCompileEnv` calls (`BackendObject_DirectVulkan.cpp:388-390,786-788`) run before any fill and would otherwise be `Fatal{…@}` — but it is an undeclared deviation, and it means the sticky stamp (`FilledGen = 1`, `PipeFill.cpp:530-535`) and the `Sticky → FilledGen != 0` branch are consulted by no accessor at all: the sticky machinery is exercised only by tests that call `MGPipeInputFieldIsFresh` directly. Record it in the deviations list (and in `MEASUREMENTS.md`), so P2's tracker does not "fix" the missing check. - -2. **`ArmVerify` re-arms only when `Features.PipeVerify` changes** (`PipeFill.cpp:362`); `PipeVerifyCorrupt` and `PipeVerifyFatal` are latched at that moment (`:367-374`) and a later change to either without a `PipeVerify` toggle is not seen. Deviation 14 and the `878db2c4` subject ("re-parse the POISON_OMIT and VERIFY knobs when their Features value changes") overstate this. Lane behaviour is unaffected (all three are loaded before the first fill); the unit tests set all three before the child's first fill. Say so precisely in the record. - -3. **`GetBufferBindingSlot(BufferTarget::Index)` diverges between arms by design and no `FillPoints.def` row can repair it.** `GLContext::GetBufferBindingSlot` is polymorphic (`Core.cpp:98-106`: `Index` returns the bound VAO's element-buffer slot); the fill copies `GlobalBufferTargets` only and leaves `Index` null (`PipeFill.cpp:63-69`), so the push accessor is `Fatal{UnmigratedPipeInput, "GetBufferBindingSlot@…"}` there (`PipeInputs.h:489-492`). Unreachable today (every backend slot read is DrawIndirect/DispatchIndirect/Parameter/PixelPack; table above), same class as v1 minor 5 (`IsCapabilityEnabledIndexed`): both belong in the same `MEASUREMENTS.md` line for the integrator, because the D.3 rule "a Fatal is fixed by adding the row" is false for these two accessors. - -4. **`VerifyFatalOffLogsTheDivergenceAndContinues` never observes the `verify summary` line** — the child `_exit(0)`s (`PipeInputsTest.cpp:446`), so `VerifyState::~VerifyState` (`PipeFill.cpp:348-354`) does not run; the summary path is covered only by my lane run (`summary=1` under `MOBILEGL_PIPE_VERIFY_FATAL=0`). Fine as a unit-level statement of "continues"; note that the teardown line's coverage lives in E's lanes. - -5. **`MGPipeVerifyReadHook` re-reads the whole field on every accessor call** (`PipeFill.cpp:430`) — for `GetRenderStateParameters` that is a 1168-byte copy plus a 65-member compare per backend read, and `GetProgramForDraw` re-joins the link per read. Declared (deviation 2) and inside the 5–10× verify budget; recorded here only so the D.3 wall-time numbers are read with this in mind, and because the `InHook` guard (`:429-432`) is what keeps a `GetProgramForDraw`-triggered backend re-entry from recursing — worth a comment at the site. - -## 4. Where the tree and the brief disagree (verified; the implementer's list stands) - -Unchanged from v1: `GL_Drawing.cpp:906` vs 907; `RenderStateParameters` 65 members not 67; `CapabilityInput` 35 not 37; c5 regenerates 1 file not 7; `integration-gpu` 868 entries; the `ctest -N` regex needs the padded form. Nothing new this round. - -## 5. Notes for the integrator (not A's findings) - -- The v1 note on the vacuous `-R 'DirectGLES\.Verify\..*Mipmap'` CI regex still stands; the implementer's record of it in `core-v2.md` is accurate. -- Once M1 is fixed, D.3 gains a new failure mode worth expecting: any backend read reached from EGL/init before the first verb will now be `Fatal{UnmigratedPipeInput, "@"}` in the verify lane — per E's risk table the answer is a fill point at that boundary (an EGL virtual), never a sticky flag or a `FillPoints.def` row. -- `GetBufferBindingSlot(Index)` and `IsCapabilityEnabledIndexed(other cap)` are the two accessors where "add the row" does not apply (minor 3): put both on one `MEASUREMENTS.md` line. - -## 6. Verdict - -Not approved: fix M1 (predicate emission in `gen_pipe.py` + regenerate, serial-0 assertion in `PipeInputFieldsStartUnfilled`, one fork case for the `@` Fatal), then re-review. Minors 1–3 are records for the deviations list / `MEASUREMENTS.md`; 4–5 are notes. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v3.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v3.md deleted file mode 100644 index 9bc6a53d..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-review-v3.md +++ /dev/null @@ -1,324 +0,0 @@ -# Adversarial review — p1-core v3 (tree `~/w7/p1-core`, branch `p1/core`, HEAD `44ffafb2`) - -Verdict: **NOT approved — 1 major, 6 minors.** - -Everything the round-2 review asked for is genuinely fixed and I reproduced each fix independently -rather than through the implementer's own tests: the serial-0 window is closed on **both** branches -of the freshness predicate (sticky included), the `PipeInputsTest` log-path race is gone (0/20 red -under `-j 8`, no leftovers), `ArmVerify` keys on all three knobs, and the FATAL=0 child now observes -its teardown summary. All eight G-gates that can be checked in this tree are green, the 83 fill -points are in the right places, and the 63 accessors are signature-identical to `GLContext`'s. - -The major is not in the rework. It is a **hole in `FillPoints.def`'s `kReadback` class mask** that -the espryt reviewer raised against package A in round 1, that is still open at HEAD, and that turns -gate G3 red on the integrated tree — the first of the two cross-package items I was asked to settle. -The second (the `FilledGen == 0` window) is genuinely closed. - -Tree left exactly as found: `git status` shows only the three untracked build directories, `git diff` -is empty, HEAD is `44ffafb2`. All reviewer artifacts under `~/w7/p1-core-review3/`; scratch inputs -under `scratchpad/wf4/core-review3/` (`gates.sh`, `nc.sh`, `lane.sh`, `probe.cpp`, `probe2.cpp`, -`sig.py`, `mut.sh`, `fixsim.sh`). Two generator mutation experiments ran in `/tmp` copies, never in -the tree, and were deleted afterwards. - ---- - -## 1. What I re-ran (all on `44ffafb2`; all three build dirs were `ninja: no work to do`) - -| check | command | observed | -|---|---|---| -| **G1 symbols** | `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0` | `.text 10792579 -> 10792579 (+0, +0.000%)`; `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed, 27060 unchanged`; Removed/Added/Resized/Renamed sections all `_none_` | -| **generator** | `python3 scripts/gen_pipe.py --check` / `--self-test` | rc 0 / rc 0; `63 PipeInputs fields (7 sticky), 69 verbs, 9 classes`, `generated files are up to date`; 6 negative controls `tripped as expected`, `positive control OK` | -| generated files match their sources | `git status --porcelain` after `--check` | only the three untracked build dirs; no `.inc` drift | -| **ctest -L unit ×3** | `ctest --test-dir {build-linux,build-push,build-verify} -L unit --no-tests=error -j 12` | `100% tests passed, 0 tests failed out of 1482` in each | -| **`ctest -N` name diff** | padded regex `sed -E 's/^ *Test +#[0-9]+: //'`, `comm` vs `~/w7/p1-before-ctest-names.txt` | 2350 names in each of the three dirs; **0 removed**; 16 added (4 `PipeCatalogue.*` + 12 `PipeInputsTest.*`); linux/push/verify name sets byte-identical (`diff` empty) | -| fill points | `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp` | Drawing 36, Framebuffer 11, Getter 3, Program 1, Query 18, Sync 6, Texture 8 = **83** over **69** distinct verbs; none outside `MG_Impl/GLImpl` | -| every table call has a fill | all `gBackendFunctionsTable.GL.` occurrences in `MG_Impl` classified | 89 occurrences; every one that is a *call* has an `MGP_FILL` on its path; the surplus occurrences are null-checks (`BeginOcclusionQuery` ×3, `GetTexImage`, `GetTextureImage`, `EndTransformFeedback`) and captures (`MemoryBarrier` ×2, `MemoryBarrierByRegion`, `DispatchCompute*`, `PatchParameteri`). No missing fill point | -| **negative control A, unit** | `PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry`, plus my own `probe2 corrupt` | see §2.2 — red for the stated reason | -| **negative control B, unit** | `PipeInputsTest.ReadingAnOmittedFieldAbortsNamingTheVerb` / `PoisonOmitKnobArmsTheOmission`, plus my own `probe2 omit` / `omit-other-verb` | see §2.2 — red on that verb only | -| negative control A, lane | `MOBILEGL_PIPE_VERIFY=1 MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters ctest --test-dir build-verify -R 'DirectGLES\..*ClearThenReadPixels'` | **red**: `1586 - DirectGLES.ClearThenReadPixelsScenario.ADrawIntoTheDefaultFramebufferSurvivesAnEarlierClear (Subprocess aborted)`; log: `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear", verb=1, where=entry}` | -| clean lane smoke | same without the knob | 5/5 green, `verify armed`=1, `Fatal{`=0, `verify read of`=0 | -| negative control B, lane | `+ MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit` | 5/5 green, `poison omission armed`=1, `Fatal{`=0 — vacuous on this tree by construction (minor 5) | -| M1(v1) log race, re-stressed | `ctest --test-dir build-verify -R PipeInputsTest -j 8` × 20 | **red = 0/20**; `ls /tmp/mobilegl-pipeinputs-test*` = 0 afterwards; log path is per-pid (`PipeInputsTest.cpp:525-526`) | -| skip visibility | `ctest -R PipeInputsTest` in the three dirs | build-verify 12/12 pass; build-push 4 skipped (verify-only + the poison-only new case); build-linux 12 skipped ("push not compiled in") | -| **M1(v2) fix, independently** | `probe2 prefill` (my own binary, linked against `build-verify`) | `serial=0: fresh fields = 0 of 63 (sticky RecordError fresh=0)`; `serial=5, all stamps 0: fresh fields = 0`; then rc **134** with `Fatal{UnmigratedPipeInput, "GetLineWidth@"}` | -| accessor signature parity | `sig.py`: all 63 accessor declarations in `PipeInputs.h` vs `MG_State/GLState/Core.h` | **63/63 identical** in name, parameters, constness and return type. The nine my regex first reported as DIFF (`IsTransformFeedbackActive`, `GetTextureContextId`, …) are one-line inline definitions in `Core.h` (`:325,326,334,357,429,441,126,130,143`) that the regex skipped; re-read by hand, all match. **No `const&` became a copy**, on the draw path or anywhere else | -| build-configuration hygiene | `ninja -t commands` in the three dirs | build-verify/build-push: **0** `MobileGL/` TUs compiled without `-DMOBILEGL_PIPE_PUSH=1` (so the `#if MOBILEGL_PIPE_PUSH` members added to `MG_Config::Features` cannot produce an ODR/layout split); build-linux: 0 occurrences of either macro and `PipeInputs.cpp`/`PipeFill.cpp` not compiled | -| D3 verbatim | `cat MobileGL/MG_Pipe/PipeInputsSwitch.h` | identical to the brief's D3 text, including the `#else` arm being the only `pGLContext` spelling | -| generator gate on the M1 fix | `mut.sh` m1: delete `if (gen == 0) return false;` from the generated `PipeFilled.inc` in a `/tmp` copy | `gen_pipe.py --check` → **rc 1** (the predicate cannot silently regress) | -| commit hygiene / ownership | `git log`, `git diff --stat 087685d1..HEAD` | 14 commits, `[Type] (Scope): …` single-line subjects, 0 `Co-Authored-By`, nothing pushed; 26 files, every path in A's C.5 rows | - ---- - -## 2. The two cross-package items I was asked to settle - -### 2.1 (a) `kReadback` and the transform-feedback flags — **still open. This is the major.** - -See §3 (M1). Answer to the question as posed: **no**, `FillPoints.def`'s `kReadback` class does not -carry `IsTransformFeedbackActive` / `IsTransformFeedbackPaused` at HEAD, and **yes**, HEAD's class -table still produces `Fatal{UnmigratedPipeInput, "IsTransformFeedbackActive@ReadPixels"}` — I -reproduced it directly against this tree's `build-verify` library, without the espryt patch. - -On the second half of the question ("whether any other verb class is missing a field its verb path -reads"): I found **no second gap**, from three independent angles — - -1. the espryt reviewer's full `DirectGLES` `integration-gpu` run under verify on an integrated tree - (441 entries) produced exactly 19 aborts, all this one Fatal (`espryt-review-v1.md:46`); -2. the magma reviewer's full `DirectVulkan` verify run produced **zero** `UnmigratedPipeInput` and - zero `@` (`magma-review-v1.md:99`); -3. my own source audit of the helpers that serve more than one verb class against the pull-site - catalogue §4.2 and §1: `SyncRenderState` (kDraw/kClear/kBlitOrCopy/kProgramOp — all four masks - carry its four accessors), `SyncNeccessaryTextures` (its five texture accessors are in every - class that reaches it, kReadback included), `SyncCurrentFBO` / - `FramebufferImpl::GetReadColorAttachment` (`Managers.cpp:5355`) and - `IsFixedPointFallbackReadAttachment` (`:5460`) read `GetFramebufferBindingSlot`, which kReadback - has; `BoundImageUnitFormat` (`Managers.cpp:6230`, `GetImageTextureBinding`) is reached only - through `ImageUnitFormatsStillMatch` from `SyncCurrentProgram` (`DirectGLES.cpp:2812`), i.e. the - draw/dispatch closure, and both masks carry the field; `MultiDraw.cpp:45,51,52,87,95` are kDraw - accessors in kDraw's mask. - - Residual exposure worth naming for D.3: **nobody has yet run the 40-trace retrace under - `MOBILEGL_PIPE_VERIFY=1`**, and that is the part of G3 that exercises paths the itest lanes do - not (MC shaders, XFB spans, indirect count). More `FillPoints.def` rows may fall out there; per - D7 the fix is always "add the row", never a sticky flag. - -### 2.2 (b) The `FilledGen == 0` / `serial == 0` window — **closed, on both branches and for sticky fields.** - -`MobileGL/MG_Pipe/generated/PipeFilled.inc:316-321` (emitted by `gen_pipe.py:813-818`): - -``` -inline Bool MGPipeInputFieldIsFresh(const MGPipeFilledState& state, MGPipeInputField field) { - const SizeT index = static_cast(field); - const Uint64 gen = state.FilledGen[index]; - if (gen == 0) return false; - return kMGPipeInputFieldSticky[index] || gen == state.CurrentVerbSerial; -} -``` - -The `gen == 0` refusal precedes the branch, so it covers the sticky arm too. Verified three ways, -none of them by trusting the delivered tests: - -``` -$ MOBILEGL_LOG_FILE_PATH=~/w7/p1-core-review3/p2-prefill.log ~/w7/p1-core-review3/probe2 prefill -serial=0: fresh fields = 0 of 63 (sticky RecordError fresh=0) -serial=5, all stamps 0: fresh fields = 0 -rc=134 -[FATAL]: MGPipe: Fatal{UnmigratedPipeInput, "GetLineWidth@"} -``` - -(the second line is the case the v2 fix's wording does not cover explicitly: a *non-zero* serial with -an unstamped sticky field is stale too, so a process that filled before a sticky field was ever -stamped cannot read it as fresh either). - -The compare-at-read branch: the hook still arms only inside `MGPipeFillForVerb` -(`PipeFill.cpp:532`), so it is blind before the first fill — but `MGP_INPUT_CHECK` precedes -`MGP_INPUT_VERIFY_READ` in every accessor body (`PipeInputs.h:199-474`) and a verify build always -defines `MOBILEGL_PIPE_POISON` (`PipeInputs.h:21-26`), pinned by -`static_assert(MOBILEGL_PIPE_POISON, ...)` at `PipeFill.cpp:349`. The probe run above sets -`Features.PipeVerify = true` before the read and still aborts on the poison first, with no -`verify armed` and no `PipeVerifyDiffer` in the log — i.e. the window is closed on that branch by -construction, not by luck. In a push build without the poison the window is open by design (both -macros compile out); that is D2's definition, not a defect. - -### 2.3 The two negative controls, exercised independently - -Driving `MG_Config::Features` exactly as `ConfigLoader.cpp:247-252` does, from my own binary -(`~/w7/p1-core-review3/probe2`, linked against `build-verify`): - -``` -===== corrupt (Features.PipeVerify=1, PipeVerifyCorrupt="GetRenderStateParameters") -rc=134 - MGPipe: verify armed - 63 fields, 69 verbs, fatal=1 - MGPipe: verify corruption armed - GetRenderStateParameters - MGPipe: Fatal{PipeVerifyDiffer, "GetRenderStateParameters@DrawArrays", verb=1, where=entry} - -===== omit (Features.PipePoisonOmit="GenerateMipmap:GetActiveTextureUnit") -draw read ok <- the preceding DrawArrays read is unaffected -sibling read ok <- GetTextureUnitObject, same verb, still fresh -rc=134 - MGPipe: poison omission armed - GetActiveTextureUnit@GenerateMipmap - MGPipe: Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"} - -===== omit-other-verb (same knob, fill BindImageTexture — same kTextureOp class) -green: BindImageTexture (same kTextureOp class) is unaffected rc=0 -``` - -Both controls turn something red, each for its stated reason, and G5's "on **that** verb and only -there" holds even against a sibling verb of the same class. The lane-level CORRUPT control is red as -well (§1). - ---- - -## 3. Major - -### M1 — `FillPoints.def`'s `kReadback` class lacks `IsTransformFeedbackActive` / `IsTransformFeedbackPaused`, which Espryt's `ReadPixels` **and** `GetTexImage` verb paths read; G3 is red on the integrated tree - -**Where.** `MobileGL/MG_Pipe/FillPoints.def:225-240` — the `kReadback` block holds 15 rows and -neither flag. The generated mask says so: -`MobileGL/MG_Pipe/generated/PipeFillPoints.inc:290-291`, `// kReadback: 22 fields (15 own + 7 sticky)`, -`{{0x5c50f3a041300541ull, 0x0000000000000000ull}}`. - -**The read, at `087685d1` and unchanged on `p1/espryt`:** - -``` -MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp:5259-5260 (ScopedEmulationDrawState::ScopedEmulationDrawState, :5226) - if (MG_State::pGLContext->IsTransformFeedbackActive() && - !MG_State::pGLContext->IsTransformFeedbackPaused() && g_GLESFuncs.glPauseTransformFeedback) { -``` - -reached as -`ReadPixels` (`:8999`, the `GLFunctionsTable.ReadPixels` entry) -→ `ReadPixelsDepthComponent` (call `:9066`, definition `:8140`) -→ `DepthStencilSamplingReadImpl::Read` (call `:8128`, definition `:8039`) -→ `ScopedEmulationDrawState emulationState;` (`:8061`) -→ the two reads at `:5259-5260`. - -`GetTexImage` (`:9166`) is in the same class and reaches the same helper through its own pack path -(`:9498`, `:9502`), so the identical Fatal is reachable as `IsTransformFeedbackActive@GetTexImage`. -The catalogue's §4.2 row for Espryt's `ReadPixels` lists only the pack/format-conversion helpers and -misses this branch entirely (`scout-pull-sites.md:893`), which is how the row was lost; the -catalogue's own §1 entry for `:5259` attributes it to `ResolveThenBlit` / "blit" only -(`scout-pull-sites.md:188-189`), and `kBlitOrCopy` does carry both flags — which is why only the -readback class is short. - -**Reproduction (this tree, `build-verify`, nothing in the tree touched).** A 40-line program -(`scratchpad/wf4/core-review3/probe.cpp`, built with `PipeInputsTest`'s own compile/link flags into -`~/w7/p1-core-review3/probe`) that does what `DirectGLES.cpp:5259` does under `MGB_CTX` on the -`ReadPixels` verb: - -``` -$ MOBILEGL_LOG_FILE_PATH=~/w7/p1-core-review3/probe.log ~/w7/p1-core-review3/probe -after ReadPixels fill: serial=1 verb=ReadPixels - fresh(IsTransformFeedbackActive)=0 fresh(IsTransformFeedbackPaused)=0 fresh(GetPixelStoreParameters)=1 -Aborted rc=134 -$ cat ~/w7/p1-core-review3/probe.log -[FATAL]: MGPipe: Fatal{UnmigratedPipeInput, "IsTransformFeedbackActive@ReadPixels"} -``` - -**Corroboration on a real lane** (not mine, but the reason this was raised against core in round 1): -`espryt-review-v1.md:46,50` — `MOBILEGL_PIPE_VERIFY=1 ctest --test-dir build-verify -L integration-gpu -R '^DirectGLES\.' -j 4` -on an integrated tree gives **421/441**, the 19 failures all `DirectGLES.ForcedDepthStencilEmulation.*` -(`MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION=1`, cases 2274-2293), each aborting on exactly this -Fatal (`~/w7/p1-espryt-review-int-one.log`). I confirmed the row is still absent on `p1/espryt` -(`f4ea84c5`) too, since that branch inherits `FillPoints.def` from `p1/contract`. - -**Why this is a major rather than a note.** G3 requires zero `Fatal{UnmigratedPipeInput}` across the -`integration-verify` lane; with this row missing, 19 known entries abort the moment core and espryt -are integrated (D.1 order: core → espryt → magma → verify), i.e. the package's own acceptance gate -cannot pass. D7 designates `FillPoints.def` — package A's file — as the *only* place such a Fatal -may be fixed ("a `Fatal{UnmigratedPipeInput}` found there is fixed by adding the row to this table, -never by marking the field sticky"), so the fix belongs to A, and the lane that is the designated -oracle has already spoken. It was reported before this round's rework began and `core-v3.md` -neither fixes it nor records it (its "Unfinished" section says "Nothing in A's scope"). - -**In fairness to the implementer:** the brief's own D7 table for `kReadback` has the same hole, and -`FillPoints.def` reproduces the brief's 139 (class, field) rows exactly — the round-2 review's -`d7check.py` verified 139/139, and `FillPoints.def` is untouched since. A followed the specification -faithfully; the specification was derived from a scout reachability trace that missed one branch. -That makes it a *correct* implementation of a *wrong* table, which is still an open defect in A's -deliverable, not somebody else's. - -**Fix, verified end to end in a `/tmp` copy** (`scratchpad/wf4/core-review3/fixsim.sh`): two rows in -`FillPoints.def`'s `kReadback` block plus a regenerate. `gen_pipe.py --check` correctly goes rc 1 on -the un-regenerated tree, and the regenerated mask is the only change: - -``` -- // kReadback: 22 fields (15 own + 7 sticky) -- {{0x5c50f3a041300541ull, 0x0000000000000000ull}}, -+ // kReadback: 24 fields (17 own + 7 sticky) -+ {{0x5f50f3a041300541ull, 0x0000000000000000ull}}, -``` - -I did **not** apply it (the tree is left as found). After applying, re-run the espryt reviewer's -lane on an integrated tree to confirm 441/441, and re-run my `probe` (it must print -`fresh(IsTransformFeedbackActive)=1` and exit 0). - ---- - -## 4. Minors - -1. **A `MOBILEGL_PIPE_VERIFY_CORRUPT` field outside every executed verb's mask arms, logs, and never - reports** — `MobileGL/MG_Impl/Pipe/PipeFill.cpp:416` - (`if (g_verify.Corrupt && MGPipeFieldMaskHas(mask, *g_verify.Corrupt))`). Observed: - `probe2 corrupt-outside-mask` (CORRUPT=`GetPixelStoreParameters`, verb `DrawArrays`) → **rc 0**, - log carries `verify corruption armed - GetPixelStoreParameters` and nothing else. Negative - control A is therefore green-by-omission whenever it is pointed at a field no executed verb - fills; only the unknown-name case is Fatal (D2). The CI step's `GetRenderStateParameters` happens - to be in kDraw/kClear/kBlitOrCopy/kReadback/kProgramOp, so today it works — but the control - cannot report its own inapplicability. One counter and a teardown `MGLOG_E` when a corruption was - armed and applied zero times would make it falsifiable. - -2. **The push arm loses the pull arm's DEBUG bounds assertion on two texture accessors.** - `PipeInputs.h:518-533` (`GetImageTextureBinding`, both overloads) and `:549-556` - (`GetTextureUnitObject`) index the raw base pointer with no range check, while - `MG_State/GLState/TextureState/TextureState.cpp:157-172` guards both with - `MOBILEGL_ASSERT(unit >= 0 && unit < MAX_TEXTURE_IMAGE_UNITS, ...)`. In an INFO build the assert - is inert (`Defines.h:113-115`), so G1/D9 are unaffected and behaviour is identical; a DEBUG push - build silently reads out of bounds where the pull build would have said so. (`GetBufferBindingPoint`'s - `index` is unchecked on both sides — `PipeInputs.h:506` vs `BufferState.cpp:93` — so it is - symmetric and not part of this.) - -3. **Nine fill points still bump the serial for a null table entry** — declared deviation 1, and I - re-audited all 83 statements against appendix B.7 rather than trusting the record. The nine are - `GL_Query.cpp:263,294,306,531,543,575,583` and `GL_Sync.cpp:179` (plus `GL_Drawing.cpp:1358`, - which is actually exact — it sits inside the `FenceSync && ClientWaitSync` guard). The **dangerous** - direction is absent everywhere: there is no call that can be reached without its fill. The - round-1 finding (`GL_Sync.cpp` `DeleteSync` in the orphan sweep) is fixed — `GL_Sync.cpp:233` is - now inside `if (backendDeleteSync && syncObject->backendHandle)`, matching its `GL_Query.cpp:930` - sibling. Placement relative to frontend mutation is right in every case that matters - (`BindImageTexture` after `.Bind`/`NoteTextureUnitTouched`, `PatchParameteri` after - `SetPatchVertices`, `Pause`/`Resume` after `SetTransformFeedbackPaused`, `BindTransformFeedback` - after `BindTransformFeedbackObject`, `EndTransformFeedback` *before* the frontend's own - `EndTransformFeedback()`, `ReadPixels` inside the `ScopedNeutralPackState` scope at - `GL_Texture.cpp:1078`). - -4. **The env-var forms of both knobs are inert in the unit binary.** `MG_ConfigLoader` never runs - there, so `MOBILEGL_PIPE_VERIFY_CORRUPT` / `MOBILEGL_PIPE_POISON_OMIT` in the environment do - nothing to `ctest -R PipeInputsTest`; the unit-level controls set `MG_Config::Features` directly - (the same fields `ConfigLoader.cpp:247-252` writes, through the same parsers). That is the right - design, but "the negative controls at unit level" should be understood as the two tests, and the - env→`Features` hop is proven only by the lane runs. Worth one sentence in `MEASUREMENTS.md` so - nobody later "checks" the controls by exporting the variables around a unit run and reads green. - -5. **The lane-level POISON_OMIT control is vacuous in this package** (declared): 5/5 green with only - `poison omission armed` in the log, because no backend on `p1/core` spells `MGB_CTX->`. Its real - coverage is the unit fork case plus E's `PoisonOmitted.` lanes on the integrated tree. Combined - with the v1 note that the brief's CI regex `-R 'DirectGLES\.Verify\..*Mipmap'` matches nothing, - this control is the one the integrator should watch most closely at D.3. - -6. **`core-v3.md`'s "Unfinished: Nothing in A's scope" is not accurate** while M1 stands, and the - result file's deviation list does not mention the `kReadback` row at all even though the espryt - review named it as a core item before this round started. Bookkeeping, but it is the kind of - bookkeeping that lets a known-open item reach D.3 unowned. - ---- - -## 5. Where the tree and the brief disagree (verified; the implementer's list stands) - -Unchanged from v1/v2: `GL_Drawing.cpp:906` vs 907; `RenderStateParameters` 65 members not 67; -`CapabilityInput` 35 not 37; c5 regenerates 1 file not 7; `integration-gpu` 868 entries; the -`ctest -N` regex needs the padded form. New this round: the brief's **D7 `kReadback` field list is -itself incomplete** (M1) — when the row is added, `BRIEF-P1.md`'s D7 table and -`scout-pull-sites.md:893` should be corrected in the same pass, or the next reader will "fix" the -`.def` back to the brief. - -## 6. Notes for the integrator (not A's findings) - -- The magma reviewer's verify lane found a genuine `where=read` divergence, - `Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays", verb=3, where=read}` on - eight `DirectVulkan` scenarios (`magma-review-v1.md:99`): Magma reads that counter four times - inside one draw and the backend's own texture/sampler completion bumps it between the reads. Per - D.3 that is recorded in `MEASUREMENTS.md` and decided by the plan owner, not silenced — it is the - compare-at-read arm doing exactly its job, and it is not a core defect. -- Nobody has yet run the 40-trace retrace under `MOBILEGL_PIPE_VERIFY=1`. That is the half of G3 - most likely to surface further `FillPoints.def` rows; budget the "one day of D.3 iteration" E's - risk table already names. -- `GetBufferBindingSlot(BufferTarget::Index)` and `IsCapabilityEnabledIndexed(cap ∉ {Blend, ScissorTest})` - remain the two accessors where "add the row" does not apply; the one-line `MEASUREMENTS.md` record - the implementer drafted is still owed. - -## 7. Verdict - -**Not approved.** One major: add `X(kReadback, IsTransformFeedbackActive)` and -`X(kReadback, IsTransformFeedbackPaused)` to `MobileGL/MG_Pipe/FillPoints.def`, regenerate, and -re-run the espryt verify lane (or, in this tree, `~/w7/p1-core-review3/probe`, which must then exit -0). Minors 1-2 are small code changes worth taking in the same pass; 3-6 are records. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v1.md deleted file mode 100644 index c97c4400..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v1.md +++ /dev/null @@ -1,82 +0,0 @@ -# P1 package A (p1-core) — c2..c6 on top of the contract - -Tree `~/w7/p1-core`, branch `p1/core`, base `feat/disaggregated @ 087685d1`, contract `bf86b1ed` (tag `p1/contract`, see `core-contract.md`). Nothing pushed. Working tree clean apart from the three untracked build directories. - -## Commits (oldest first, all on `p1/core` after `p1/contract`) - -| sha | subject | -|---|---| -| `3aa4d8af1ff2ca6f95c9ab3e047e70bc0e0cf65f` | `[Feat] (Pipe): fill PipeInputs per verb class from GLContext and stamp per-verb generations - a read of a field the verb did not fill is Fatal{UnmigratedPipeInput}` | -| `a196ada4c11b59f23587596486d265124ea06a6f` | `[Feat] (Impl): call MGP_FILL before every GLFunctionsTable entry - 83 statements over 69 verbs, a no-op in the pull build` | -| `275dd3edb4909fe2a23af0f9a2667fb8215d24be` | `[Feat] (Pipe): the MOBILEGL_PIPE_VERIFY shadow comparator - a per-verb entry compare over the fill set and a compare-at-read in every accessor, first differing field and verb serial, fatal by default` | -| `83b16561c20428dd15995ed3b2b4ad5496a97bab` | `[Feat] (Pipe): give the six value structs G4 field lists and assert the lists cover their members - the memcmp fallback is now a compile error, floats in vector types compare bitwise` | -| `440d3c52534e2c227b8b4d83251d8afcb0849e3f` | `[Test] (Pipe): pin the P1 poison and verify shapes - 63 fields, 69 verbs, the seven sticky fields, an omitted GenerateMipmap field leaves exactly that field stale, a corrupted snapshot names its field, reading an unfilled field aborts with SIGABRT` | - -`git diff --stat p1/contract..HEAD`: 17 files changed, 1587 insertions(+), 72 deletions(-). Files touched (all inside A's ownership row of C.5): `MobileGL/MG_Backend/MGPipe/PipeInputs.{h,cpp}`, `MobileGL/MG_Impl/Pipe/PipeFill.{h,cpp}`, the seven `MobileGL/MG_Impl/GLImpl/{Drawing,Framebuffer,Texture,Getter,Program,Query,Sync}/*.cpp`, `MobileGL/MG_Pipe/PipeFields.def`, `MobileGL/MG_Pipe/generated/PipeVerify.inc`, `scripts/gen_pipe.py`, `MobileGL/MG_Test/Pipe/{CMakeLists.txt, PipeCatalogueTest.cpp, PipeInputsTest.cpp}`. Not touched: `MG_Backend/DirectGLES/**`, `MG_Backend/DirectVulkan/**`, `PipeCalls.def`, `Coverage.def`, `FillPoints.def`, `PipeInputsSwitch.h`, `CMakeLists.txt` (root), `Config.h`/`ConfigLoader.cpp`, fixtures. No signature in `PipeInputs.h` / `PipeFill.h` / `PipeInputsSwitch.h` that B/C/E code against changed (the accessor set, `MGB_CTX`, `MGP_FILL`, the knob names and log lines are as in the contract); the additions are listed under deviations 5-7. - -## What each commit does - -- **c2 filler** (`PipeFill.cpp`): `MGPipeFillForVerb` walks `kMGPipeClassFieldMask[kMGPipeVerbClass[verb]]`; `MGPipeFillAccess::CopyField` is one switch over the 56 stored fields, each branch calling the `GLContext` accessor of the same name (D4's fill column verbatim: loops of 8/16/32, `GlobalBufferTargets`/`BufferBindPointTargets` for the slot/point bases, `&ctx.GetTextureUnitObject(0)`/`&ctx.GetImageTextureBinding(0)` as array bases, Blend x8 + ScissorTest x16 for the indexed capabilities, all 35 capabilities incl. `FramebufferSrgb`'s constant false); every copied field is stamped with the new serial; the seven sticky fields get `FilledGen = 1` on the first live fill. `MOBILEGL_PIPE_POISON_OMIT` (`:`) is parsed once at the first fill; `MGPipeSetPoisonOmission(verb, field)` is the programmatic form; the omitted pair keeps its value copy and loses only its stamp; unknown names are `Fatal{PipeVerifyBadKnob}`. -- **c3 fill points**: the 83 `MGP_FILL(X);` statements of B.7 (36/11/8/3/1/18/6 per TU) plus `#include ` after each TU's last `MG_State`/`MG_Backend` include, inserted by a patcher (`scratchpad/wf4/core/patch_c3.py`) that refused any target line not spelling its verb. -- **c4 comparator** (`PipeFill.cpp`, `PipeInputs.{h,cpp}`): entry compare at the end of every fill (`SnapshotFromGLContext` into a file-static second `PipeInputs`, `MOBILEGL_PIPE_VERIFY_CORRUPT` applied to the snapshot arm, `MGPipeVerifyInputs` over the mask, first differing field); compare-at-read through `MGP_INPUT_VERIFY_READ` -> `MGPipeVerifyReadHook(*this, field, i0, i1)` (re-reads the field from the live context into a scratch block and compares against the live block only); `MGLOG_F("MGPipe: Fatal{PipeVerifyDiffer, \"%s@%s\", verb=%llu, where=%s}")` + `abort()` unless `MOBILEGL_PIPE_VERIFY_FATAL=0` (counts, `MGLOG_E` summary at teardown); `MGLOG_I("MGPipe: verify armed - 63 fields, 69 verbs, fatal=N")`, `MGLOG_I("MGPipe: verify corruption armed - F")`, `MGLOG_I("MGPipe: poison omission armed - F@V")`; a push build without the comparator answers `MOBILEGL_PIPE_VERIFY=1` with one `MGLOG_W_ONCE`. -- **c5 G4 growth** (`PipeFields.def`, `gen_pipe.py`, regenerated `PipeVerify.inc`): the six field lists (RenderStateParameters 65, PixelStoreParameters 8, PerBufferBlendState 7, StencilFaceState 7, DynamicBackendParameters 85, MGHostSpan 4), `kMGPipeVerifiedPayloadCount` 63 -> 69; `MEMCMP_FALLBACK_TYPES` empty and the fallback branch `static_assert(sizeof(T) == 0, "no field list in PipeFields.def for this type")`; `Array` overload; VecBase detected by a probe and compared bitwise over `data`; `check_field_lists_cover_struct_members()`, `scan_live_accessors()` (both modes), `--self-test` with six negative controls and a positive one. -- **c6 tests**: the four `PipeCatalogue.*` additions and the five `PipeInputsTest.*` cases (new unit target, own `main()` for `MOBILEGL_LOG_FILE_PATH`, fork/waitpid, never `EXPECT_DEATH`). - -## Verification (WSL, `~/w7/p1-core`; logs under `~/w7/p1-core-*.log`) - -Every command below was run on the final tree (`440d3c52`) unless a commit is named. - -| command | actual output | -|---|---| -| `python3 scripts/gen_pipe.py --check` | `gen_pipe: 71 calls (11 screen, 60 context), 69 verify payloads, 63 PipeInputs fields (7 sticky), 69 verbs, 9 classes` / `inventory 477 rows: ... 0 UNMAPPED` / `1 accessor row(s) no backend reads: GetBoundTransformFeedbackName` / `generated files are up to date`, rc=0 | -| `python3 scripts/gen_pipe.py --self-test` | six controls "tripped as expected" (struct member without F, F that is not a member, payload with no struct, verb missing from FillPoints.def, verb not in GLFunctionsTable, field row naming a non-accessor), `positive control OK`, rc=0. Before the whitespace-tolerant fix the three FillPoints controls reported DID NOT TRIP and the run exited 1 - the "zero trips is an error" arm was exercised. | -| `cmake --build build-linux` + `ctest -L unit --no-tests=error` | rc=0; `100% tests passed, 0 tests failed out of 1475` (1466 before c6 + 9 additions) | -| `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`. **G1 holds** after every commit (checked at c3, c4, c5, final). `readelf -S` section sizes identical. `cmp -l` shows 34 differing bytes: 20 in `.note.gnu.build-id`, 14 in `.rodata` = the `GIT@5d99ee4` -> `GIT@087685d` configure stamp and glslang's `Timestamp: 2026-09-05T23:14:30` -> `2026-09-06T01:32:05` string - build stamps of equal length, not code. (Markdown/JSON: `~/w7/p1-core-symbol-final.{md,json}`.) | -| `grep -rc "pGLContext" MobileGL/MG_Backend \| sort \| diff - ~/w7/p1-before-pglcontext.txt` | only the two contract files appear, both `:0` (`MG_Backend/MGPipe/PipeInputs.cpp:0`, `PipeInputs.h:0`); every catalogued file's count unchanged - A converts no site | -| `grep -c "pGLContext" MobileGL/MG_Backend/MGPipe/PipeInputs.h PipeInputs.cpp` | `0` `0` | -| `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp \| awk ...` | `83` (Drawing 36, Framebuffer 11, Texture 8, Getter 3, Program 1, Query 18, Sync 6) | -| `cmake --build build-push` + `ctest -L unit --no-tests=error` | rc=0; `100% tests passed, 0 tests failed out of 1475`; `MOBILEGL_PIPE_PUSH=1` on every compile command, `MOBILEGL_PIPE_VERIFY=1` on none | -| `cmake --build build-verify` + `ctest -L unit --no-tests=error` | rc=0; `100% tests passed, 0 tests failed out of 1475`; `MOBILEGL_PIPE_VERIFY=1` on all 497 compile commands | -| `ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4` (at c3, `a196ada4`) | `99% tests passed, 1 tests failed out of 868`; the one failure `DirectVulkan.PointSizeDemotion.PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn` (0.23 s) passes alone in build-push AND in build-linux (`100% tests passed ... out of 4`) - a `-j 4` flake of an env-pinned lane, the case the integrator script's `--rerun-failed -j 1` exists for; zero `Fatal{` lines in the log | -| `ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4` (final tree) | ITEST_FINAL_PLACEHOLDER | -| `ctest -N` name diff (pull): `comm -23 ~/w7/p1-before-ctest-names.txt -` | 2343 names, **0 removed**, 9 added (`PipeCatalogue.{VerbTableIsTheFunctionTable, StickyFieldsAreExactlyTheSeven, FloatVectorsCompareBitwise, SixValueStructsHaveFieldLists}`, `PipeInputsTest.{OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale, ReadingAnOmittedFieldAbortsNamingTheVerb, ReadingAFilledFieldCompletes, CorruptedSnapshotFieldIsNamedWithItsSerial, EveryVerbFillsItsClassAndNothingElse}`); the same 9 names exist in all three configurations (skips are visible, not vanishing). The padding-tolerant regex `'^\s+Test\s+#[0-9]+:'` is used, as in the contract report. | -| **G4 unit** (`build-verify`, `PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial`) | Passed: clean snapshot compares equal over `kDraw`'s mask; `MGPipeApplyVerifyCorruption(snapshot, GetRenderStateParameters)` -> `MGPipeVerifyInputs` false naming `GetRenderStateParameters`, whose stamp equals the fill serial; a corruption of a field outside the mask is not seen; a forwarded field cannot be corrupted. Skips visibly in build-push ("verify not compiled in") and build-linux ("push not compiled in"). | -| **G4 lane-level smoke** (`build-verify`, `MOBILEGL_PIPE_VERIFY=1 ctest -R 'DirectGLES\..*ClearThenReadPixels'`) | clean: `100% tests passed ... out of 5`, log has `MGPipe: verify armed - 63 fields, 69 verbs, fatal=1`, 0 `Fatal{`. With `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters`: `0% tests passed, 5 tests failed out of 5` (rc=8), log has `MGPipe: verify corruption armed - GetRenderStateParameters` and `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear", verb=1, where=entry}` | -| **G5 unit** (`build-verify`, `PipeInputsTest.{Omitting..., ReadingAnOmittedFieldAbortsNamingTheVerb, ReadingAFilledFieldCompletes}`) | all Passed: with `SetPoisonOmission("GenerateMipmap","GetActiveTextureUnit")` the GenerateMipmap fill leaves `GetTextureUnitObject` fresh and `GetActiveTextureUnit` stale, a following DrawArrays fill leaves it stale while `GetBoundVertexArray` is fresh, and a BindImageTexture fill (same class, different verb) heals it; the forked child that draws, reads a filled field, fills GenerateMipmap, reads the sibling and then the omitted field dies with `WTERMSIG == SIGABRT` and the log carries exactly `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}`, no `@DrawArrays`, no `GetTextureUnitObject@`; the sibling child without the omission `_exit(0)`s with no `Fatal{`. In build-push (Release/INFO, no POISON) the two poison cases skip visibly and `ReadingAFilledFieldCompletes` passes. | -| `nm -D --defined-only build-verify/libMobileGL.so \| grep -c MGPipeVerifyInputs` / same on build-push | `1` / `0` - the symbol E's retrace-verify job probes exists only in the verify library | -| `EveryVerbFillsItsClassAndNothingElse` (build-verify) | Passed: after each of the 69 fills a field is fresh iff its class bit is set (sticky included); `ContextIdentity()` is the live context address | - -Not run: the 40-trace retrace under verify and the `integration-verify` lane (E's files; D.3 on the integrated tree). The verify-build integration-gpu was only smoked (5 `ClearThenReadPixels` entries) - on this tree no backend reads `gPipeInputs`, so its full run would only repeat build-push's proof. - -## Deviations from the brief (with reasons) - -1. **Nine fill points precede a guarded expression rather than the bare call** (D7 says "after every early return the call is behind; a verb whose table entry is null never bumps"): `GL_Query.cpp` 260/300 (`if (handle && getQueryResult64 && !getQueryResult64(...))`), 289 (`outValue = (!f || f(h)) ? 1 : 0`), 522/533/645 (`handle = f ? f() : nullptr`), 774 (`supported = ... && f && f()`), `GL_Sync.cpp` 177 (`signaled = !f || !h || f(h)`) and `GL_Drawing.cpp` 1331 (`if (auto sync = backendGL.FenceSync())`, inside the `FenceSync && ClientWaitSync` guard so this one is exact). At the other eight the fill precedes the statement that holds both the null guard and the call, so a null entry bumps the serial once with nothing to read it (kQuery fills one field; no backend read happens between two fills without a fill). Chosen over a comma-expression (`(MGP_FILL(X), f(...))`) or splitting the statements because either would touch the code shape of the pull build, and G1 asks for byte identity. -2. **Compare-at-read re-reads the WHOLE field, not only the indexed element** (D8: "re-reads the same accessor with the same indices"). The hook copies the field from the live context into a scratch block through the same `CopyField` the filler uses and compares with `MGPipeInputsFieldEqual`; the indices decorate the report (`MGLOG_E` before the Fatal). A superset: a divergence in an index the backend did not ask for is still a boundary-vs-live divergence. One copy switch instead of a second 56-way index-aware switch. -3. **`MGPipeVerifyReadHook` takes the block as its first parameter** (`(const PipeInputs& self, field, i0, i1)`; the brief spells `(f, i0, i1)`): the macro expands inside member functions of any `PipeInputs` (the snapshot's accessors run in the tests), and only the live block is verified; without `self` the hook could not tell them apart. No backend code spells the hook. -4. **VecBase bitwise compare is a probe inside the generic `MGPipeFieldEqual`, not an overload** (D8 says "two overloads ... partial ordering picks these over `const T&`"). For `FloatVec4` the overload on `const VecBase&` needs a derived-to-base conversion and LOSES overload resolution to the exact-match generic template before partial ordering is consulted; the `Array` overload is exact and does win, and is emitted as the brief says. `PipeCatalogue.FloatVectorsCompareBitwise` pins the behaviour (NaN equals itself, -0.0f != 0.0f, `a == b` is IEEE and disagrees). -5. **`MGPipeInputsFieldEqual` takes `const PipeInputs&`** (the contract had `PipeInputs&`), with a const `VisitStorage` overload added; nobody outside A calls it. -6. **`MGPipeVerifyInputs` carries `__attribute__((visibility("default")))`** (not in the brief): the library is built with hidden visibility, so without it E's `nm -D build-linux/libMobileGL.so | grep -q MGPipeVerifyInputs` step (C.4 (c)) could never pass. No MGPipe symbol was in the dynamic table before; this is the only one now. -7. **Pointer corruption flips the low bits instead of nulling** (D8 says "swap a pointer for nullptr"): a pointer that is already null (an O-class field before the first fill, or a null `GetProgramForDraw`) would make the corruption invisible to the compare; a `SharedPtr` becomes an aliasing pointer with no control block. The snapshot is only ever compared, never dereferenced. -8. **`SnapshotFromGLContext` and `MGPipeFillForVerb` share `CopyField`** rather than being two copies of the copy logic; the snapshot is still its own function (the branch that survives P13) with its own file-static block. -9. **`CurrentVertexAttributeValue` compares bitwise** in `PipeInputs.cpp` with a `sizeof == 48` static_assert: it is `MG_State` storage (three scalar `Array`s, no padding), not a payload, so it gets no `PipeFields.def` row and the struct-member check does not search `Core.h`. -10. **`PipeInputsTest` is a separate unit target** (the brief allowed it "if the verify-only tests need it"): the fork cases need their own `main()` to point `MOBILEGL_LOG_FILE_PATH` at a file before anything logs, and the fake-context cases need `MobileGL_s`; `PipeCatalogueTest` stays context-free. Its five cases are `GTEST_SKIP` in a pull build and the poison/verify cases skip in a push build without those switches. -11. **A push build without POISON acknowledges `MOBILEGL_PIPE_POISON_OMIT` with `MGLOG_W_ONCE`** ("no stamp exists to omit") instead of the `MGLOG_I("poison omission armed")` line, which is emitted only where the omission can act. -12. **The sticky one-time stamp moved from an unconditional loop into the mask walk** (every class mask carries the seven sticky bits, so the result is identical). -13. `gen_pipe.py --self-test` also runs the field-list coverage check on the real inputs first (`main()` order: calls -> payloads -> coverage check -> coverage -> self-test), so a broken real list fails before the canned controls run. - -## Where the tree contradicted the brief's numbers - -- **B.7 `GL_Drawing.cpp:907 MemoryBarrier (TextureBarrier)`**: line 907 is the closing brace; the `memoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);` call is at **906**. Every other B.7 line number held (the patcher checks each target line for its verb; the six XFB sites use abbreviated captured names `beginXfb`/`endXfb`/`pauseXfb`/`resumeXfb`/`deleteXfb`/`bindXfb`, `GL_Getter.cpp:1176` spells `getIntegeri(`). -- **B.7 continuation lines**: seven listed lines are the call's continuation line of a multi-line statement (`GL_Query.cpp` 261, 301, 523, 534, 646, 775; `GL_Sync.cpp` 178 for the 177 entry) - the fill goes before the statement start (260, 300, 522, 533, 645, 774, 177). -- **D8 "all 67 members of `struct RenderStateParameters`"**: the struct has **65** direct data members (`MGPipeValueTypes.h:239-387`, `MAX_VIEWPORTS` is static). `DynamicBackendParameters` has 85 (brief gives no count). -- **D4 `IsCapabilityEnabled` "37 entries, `MGPipeValueTypes.h:168-204`"**: `CapabilityInput` has **35** enumerators before `CapabilityInputCount`; the storage is sized from the enum, so nothing depends on the number. -- **C.1 "7 files change" for c5's regeneration**: only `PipeVerify.inc` changes at c5 (the field lists feed G4 alone); the contract commit already regenerated `PipeFilled.inc` and created `PipeFillPoints.inc`. -- **The integration-gpu count**: 868 entries in build-push's `integration-gpu` label (the brief's "371 + 3 new cases" is E's post-integration figure). -- **`ctest -N` regex** (`'^\s+Test #'`, C.1): as recorded in the contract report, ctest right-aligns the test number, so the padding-tolerant regex is what produces the 2334/2343 name lists. - -## Unfinished / notes for the integrator - -- Nothing in A's scope is unfinished. The verify library's full `integration-gpu`, the `integration-verify` lane and the 40-trace retrace under verify are D.3 items on the integrated tree (they need B/C's sites on `MGB_CTX` and E's lanes to mean anything). -- `MGB_CTX_LIVE`-guarded `RecordError` and the two `InvalidateCompileEnv` calls at backend init run before any fill; they are sticky and pass the poison from the first live fill on, as designed. If the verify lane finds an `@` Fatal for any other field, the answer is a fill point at that boundary, never a sticky flag. -- The `-j 4` flake of `PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn` reproduced neither alone nor in the pull build; it is not caused by this branch but the integrator's `--rerun-failed -j 1` step stays worth keeping. -- `scan_live_accessors()` will start reporting `MGB_CTX->` reads the day B/C land; today it reports one unread row (`GetBoundTransformFeedbackName`, dead by design). diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v2.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v2.md deleted file mode 100644 index 34495c07..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v2.md +++ /dev/null @@ -1,80 +0,0 @@ -# P1 package A (p1-core) — rework round 2 (v2) - -Tree `~/w7/p1-core`, branch `p1/core`, base `feat/disaggregated @ 087685d1`, contract `bf86b1ed` (tag `p1/contract`). v1 HEAD was `440d3c52` (reviewed in `core-review-v1.md`: 1 major, 8 minors). Four new commits on top; nothing pushed; working tree clean apart from the three untracked build directories. `core-v1.md` remains the record of c2..c6; this file records the rework and the full re-verification on the new HEAD. - -## Commits (oldest first, on top of `440d3c52`) - -| sha | subject | review item | -|---|---|---| -| `510ecd92` | `[Fix] (Impl): move the DeleteSync fill of the orphan sweep inside its null-entry guard` | minor 1 | -| `77ecde15` | `[Fix] (Pipe): refuse an eighth sticky row at compile time - the forwarded count equals the generated sticky count` | minor 8 | -| `878db2c4` | `[Fix] (Pipe): re-parse the POISON_OMIT and VERIFY knobs when their Features value changes, not once per process` | minor 4 (enabler) | -| `d9f4698d` | `[Test] (Pipe): give every PipeInputsTest process its own log file, and cover the compare-at-read arm, the three knob parsers and VERIFY_FATAL=0 with forked children` | **M1**, minors 2, 3, 4 | - -`git diff --stat 440d3c52..HEAD`: 4 files, +283/-37 — `MobileGL/MG_Backend/MGPipe/PipeInputs.h` (+4), `MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp` (+1/-1), `MobileGL/MG_Impl/Pipe/PipeFill.cpp` (+15/-2), `MobileGL/MG_Test/Pipe/PipeInputsTest.cpp` (rewritten). All inside A's C.5 rows; no B/C/E file, no fixture, no generated file (`gen_pipe --check` clean), no signature that B/C/E code against changed (`MGB_CTX`, `MGP_FILL`, the 63 accessors, the knob names and every log line are as in the contract). - -## What was done per review item - -- **M1 (shared log path)** — `PipeInputsTest::main()` names the file `mobilegl-pipeinputs-test-.log` (`ProcessId()` = `getpid`/`_getpid`) and removes it after `RUN_ALL_TESTS`; a forked child inherits the path on purpose (it is the same file the parent reads the delta from). Proof: `ctest --test-dir build-verify -R PipeInputsTest -j 8` × 40 → **0/40 red** (was 40/40 red at v1 per the review); build-push × 10 → 0/10 red; `/tmp/mobilegl-pipeinputs-test*` leftover count after the whole suite: see the verification table. -- **Minor 1 (`GL_Sync.cpp` DeleteSync sweep)** — the fill moved inside `if (backendDeleteSync && syncObject->backendHandle)`, the shape of `GL_Query.cpp`'s `DeleteBackendQuery` sweep; D7 now holds there (a null entry never bumps). Fill count still 83 (Sync 6). Pull build unaffected (`MGP_FILL` is `((void)0)`): G1 re-proven below. -- **Minor 2 (compare-at-read arm had no falsifier)** — `PipeInputsTest.MutatedFieldIsNamedAtRead` (verify build, fork): child sets `Features.PipeVerify = true`, fills `DrawArrays`, reads `GetLineWidth()` (completes, boundary == live) and `GetRenderStateParameters()`, then `pGLContext->SetLineWidth(boundary + 1)` and reads `GetLineWidth()` again; parent asserts `SIGABRT`, the arming line `verify armed - 63 fields, 69 verbs, fatal=1`, `verify read of GetLineWidth (index 0, 0) differs from the live context`, exactly `Fatal{PipeVerifyDiffer, "GetLineWidth@DrawArrays", verb=, where=read}` and no `where=entry`. Passes in build-verify; visible skip elsewhere. -- **Minor 3 (name overclaimed)** — `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale` now loops all 63 fields after the omitted `GenerateMipmap` fill: fresh iff the bit is in `kMGPipeVerbClass[GenerateMipmap]`'s mask and the field is not `GetActiveTextureUnit`. -- **Minor 4 (knob parsers unreachable from a unit test)** — the two latches in `PipeFill.cpp` now key on the VALUE rather than "parsed once": `ParsePoisonOmissionKnob` re-parses when `Features.PipePoisonOmit` differs from the last parsed string; `ArmVerify` re-arms when `Features.PipeVerify` differs from `g_verify.Enabled` (and resets `Corrupt` on re-arm). A lane loads `Features` before its first fill, so lane behaviour is one parse/arm per process as before; a forked test child that sets `Features` after its parent filled gets its own parse. Cost: one `String ==` (two empty strings in every shipped configuration) and one `Bool ==` per fill, push/verify builds only. Five new fork cases cover: the OMIT knob arming through `Features` (arming line + the exact `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}`), a bad verb in OMIT (`Fatal{PipeVerifyBadKnob, "MOBILEGL_PIPE_POISON_OMIT=NoSuchVerb:GetActiveTextureUnit": no such verb in kMGPipeVerbNames}`; this one runs in build-push too — the parser is switch-free), the CORRUPT knob through `Features` (`verify corruption armed - GetRenderStateParameters` + `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@DrawArrays", verb=N, where=entry}`), a bad CORRUPT field (`Fatal{PipeVerifyBadKnob, "MOBILEGL_PIPE_VERIFY_CORRUPT=NoSuchField": no such field in kMGPipeInputFieldNames}` and no arming line), and `PipeVerifyFatal = false` (child exits 0 after two fills, log has both `PipeVerifyDiffer` lines at consecutive serials, `fatal=0` in the arming line). The `MGLOG_W_ONCE` for OMIT in a POISON-less push build and for `MOBILEGL_PIPE_VERIFY=1` in a comparator-less build stay lane-verified only (the review's table) — they are one-shot log lines and the review already exercised them. -- **Minor 5 (`IsCapabilityEnabledIndexed` asymmetry)** — record only (below, for the integrator's `MEASUREMENTS.md`); no code change: the push arm's Fatal for a cap other than Blend/ScissorTest is D6's rule for a field the block does not carry, and no backend asks another cap. -- **Minor 6 (GIT@ stamp)** — record: all three libraries still carry `GIT@83b1656` (the stamp is a configure-time value; no reconfigure happened in this round, only incremental builds, which relinked `build-linux/libMobileGL.so` because `GL_Sync.cpp` changed). The G1 comparison below is of the relinked library. -- **Minor 7 (3 vs 7 generated files)** — record only; `gen_pipe --check` clean. -- **Minor 8 (hand-written 7)** — `static_assert(kMGPipeForwardedFieldCount == kMGPipeInputStickyFieldCount)` in `PipeInputs.h` (the generated constant is visible through `MGPipe.h` → `generated/PipeFilled.inc`). -- **Integrator note in the review (vacuous `-R '...Mipmap'` CI regex)** — not A's file; recorded under "for the integrator" below. - -## Verification (WSL, `~/w7/p1-core`, HEAD `d9f4698d`; logs `~/w7/p1-core-v2-*.log`, summary `~/w7/p1-core-v2-summary.txt`) - -Builds: `cmake --build {build-linux,build-push,build-verify} -j 12` all rc=0 (`~/w7/p1-core-v2-build.log`), `CCACHE_BASEDIR=/home/swung/w7`. - -| command | actual output | -|---|---| -| `python3 scripts/gen_pipe.py --check` | rc=0, `gen_pipe: generated files are up to date` | -| `python3 scripts/gen_pipe.py --self-test` | rc=0, 6 controls `tripped as expected`, `positive control OK` | -| `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0` | rc=0; `.text 10792579 -> 10792579 (+0, +0.000%)`; `Total 17209819 -> 17209819 (+0)`; `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed, 27060 unchanged` (`~/w7/p1-core-v2-symbol.md`) | -| `cmp -l before after \| wc -l` / `readelf -SW` diff | 34 bytes (build-id + the two build stamps, as at v1); section table identical | -| `strings ... \| grep GIT@` | `GIT@83b1656` in all three libraries (configure-time stamp; see minor 6) | -| `grep -rc pGLContext MobileGL/MG_Backend \| sort \| diff - ~/w7/p1-before-pglcontext.txt` | only `MG_Backend/MGPipe/PipeInputs.{cpp,h}:0` appear; every catalogued count unchanged | -| `grep -c pGLContext MobileGL/MG_Backend/MGPipe/PipeInputs.{h,cpp}` | `0` `0` | -| `grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp` | **83**: Drawing 36, Framebuffer 11, Getter 3, Program 1, Query 18, Sync 6, Texture 8 | -| `ctest --test-dir build-linux -L unit --no-tests=error -j 12` | rc=0, `100% tests passed, 0 tests failed out of 1481` | -| `ctest --test-dir build-push -L unit --no-tests=error -j 12` | rc=0, `100% tests passed, 0 tests failed out of 1481` | -| `ctest --test-dir build-verify -L unit --no-tests=error -j 12` | rc=0, `100% tests passed, 0 tests failed out of 1481` | -| `ctest -N` names vs `~/w7/p1-before-ctest-names.txt` (regex `'^\s+Test\s+#[0-9]+:'`) | 2349 names in each of the three dirs; `comm -23 before now` = **0 removed**; 15 added = the 4 `PipeCatalogue.*` of v1 + 11 `PipeInputsTest.*` (the v1 five + `MutatedFieldIsNamedAtRead`, `PoisonOmitKnobArmsTheOmission`, `BadPoisonOmitKnobIsFatalNamingTheKnob`, `VerifyCorruptKnobNamesTheFieldAtEntry`, `BadVerifyCorruptKnobIsFatalNamingTheKnob`, `VerifyFatalOffLogsTheDivergenceAndContinues`); push and verify name sets identical | -| `ctest -R PipeInputsTest -V`, skip visibility | build-linux 11 `***Skipped` ("push not compiled in"); build-push 9 skipped (5 "verify not compiled in", 4 "poison not compiled in"), `ReadingAFilledFieldCompletes` and `BadPoisonOmitKnobIsFatalNamingTheKnob` pass; build-verify 0 skipped, 11/11 pass | -| **M1 repro, fixed**: `ctest --test-dir build-verify -R PipeInputsTest -j 8` × 40 | **red=0/40** (`~/w7/p1-core-v2-stress-{1..40}.log`); v1 was 40/40 red per the review | -| `ctest --test-dir build-push -R PipeInputsTest -j 8` × 10 | red=0/10 | -| `nm -D --defined-only \| grep -c MGPipeVerifyInputs` | push 0, verify 1 | -| `ctest --test-dir build-push -L integration-gpu --no-tests=error -j 4` | PENDING-ITEST | -| leftover `/tmp/mobilegl-pipeinputs-test*` after the whole suite | PENDING-LEFTOVER | -| G4 unit (`CorruptedSnapshotFieldIsNamedWithItsSerial`, `VerifyCorruptKnobNamesTheFieldAtEntry`, `MutatedFieldIsNamedAtRead`) | pass in build-verify: entry compare names the corrupted field at its serial (block level and through the knob), and the compare-at-read arm names a mutated field `where=read` — both arms now have a falsifier | -| G5 unit (`OmittingOneField...`, `ReadingAnOmittedFieldAbortsNamingTheVerb`, `PoisonOmitKnobArmsTheOmission`, `ReadingAFilledFieldCompletes`) | pass in build-verify (the two poison-free ones also in build-push): the exact `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}`, no `@DrawArrays`, no `GetTextureUnitObject@`; the sibling exits 0 with no `Fatal{` | -| commit hygiene | 4 commits, `[Type] (Scope): ...` + blank + `- ` bullets, no Co-Authored-By/attribution, nothing pushed; `git status` clean; all LF | - -Not run (unchanged from v1): the 40-trace retrace under verify and the `integration-verify` lane (E's files, D.3 on the integrated tree). - -## Deviations from the brief (with reasons) — additions to v1's list 1-13 - -14. **The knob latches key on the value, not "once per process"** (`PipeFill.cpp`; the brief/contract said "parsed once at the first fill"). Reason: without it no unit test can reach the parsers or `Fatal{PipeVerifyBadKnob}` (review minor 4); lane behaviour is unchanged because `Features` is loaded before the first fill. Cost is one empty-string compare and one Bool compare per fill in push/verify builds only. -15. **`ArmVerify` resets `Corrupt` on re-arm** — a consequence of 14 (a stale corruption must not outlive the knob value that named it). -16. **v1 deviation 1's "nine guarded-expression sites" is now exactly nine**: the tenth, undeclared one (`GL_Sync.cpp` DeleteSync sweep) was moved inside its guard rather than added to the list, because the sibling `GL_Query.cpp` sweep already had the in-guard shape and D7 is satisfiable there at zero cost. -17. **`PipeInputsTest` has 11 cases, not the brief's/v1's 5**; the six additions are the review's minor 2 and minor 4 falsifiers. `PipeCatalogueTest` unchanged. -18. **`OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale` asserts the whole class mask** (the brief's "verbatim shape" names two fields); superset of the brief's assertions. - -## Where the tree contradicted the brief's numbers - -Unchanged from v1 (`GL_Drawing.cpp:906` vs 907; `RenderStateParameters` 65 members not 67; `CapabilityInput` 35 not 37; c5 regenerates 1 file not 7; integration-gpu 868 not 371+3; the `ctest -N` regex needs the padded form). New in this round: none. - -## Records for the integrator (not A's code) - -- **`IsCapabilityEnabledIndexed` asymmetry (review minor 5)**: the push accessor is `Fatal` for a cap other than `Blend`/`ScissorTest` (`PipeInputs.h`, per D6 — the block carries only those two indexed caps), the pull arm returns `false` with `MGLOG_I` (`RenderState.cpp`). Unreachable today (`VulkanRenderer.cpp` asks `Blend` only); a `MEASUREMENTS.md` line is the right place, so that a future `glIsEnabledi`-driven backend read adds a field rather than tripping the Fatal. -- **CI negative-control regex (review section 5)**: `-R 'DirectGLES\.Verify\..*Mipmap'` matches no integration test name on this tree, and E's `PoisonOmissionScenario` name has no "Mipmap"; with `--no-tests=error` the inverted step passes vacuously. The step should also grep the lane log for `Fatal{UnmigratedPipeInput, "GetActiveTextureUnit@GenerateMipmap"}`. The unit-level G5 proof (`ReadingAnOmittedFieldAbortsNamingTheVerb`, `PoisonOmitKnobArmsTheOmission`) is independent of that regex. -- **Compare-at-read in D.3**: `MutatedFieldIsNamedAtRead` is the falsifier the review asked for; a D.3 run reporting zero `where=read` divergences is now known to have a hook that fires when a value diverges. -- The `GIT@` stamp in the three libraries will refresh at the integrator's reconfigure on `~/w7/pipe`. - -## Unfinished - -Nothing in A's scope. Every major and minor in `core-review-v1.md` is either fixed (M1, minors 1, 2, 3, 4, 8) or recorded above with the reason (minors 5, 6, 7 are bookkeeping/records by the reviewer's own classification). diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v3.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v3.md deleted file mode 100644 index 73b79fd2..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/core-v3.md +++ /dev/null @@ -1,62 +0,0 @@ -# P1 package A (p1-core) — rework round 3 (v3) - -Tree `~/w7/p1-core`, branch `p1/core`, base `feat/disaggregated @ 087685d1`, contract `bf86b1ed` (tag `p1/contract`). v2 HEAD was `d9f4698d` (reviewed in `core-review-v2.md`: 1 major, 5 minors). Four new commits on top; nothing pushed; working tree clean apart from the three untracked build directories. `core-v1.md` records c2..c6 and `core-v2.md` the round-2 rework; this file records round 3 and the full re-verification on the new HEAD. - -## Commits (oldest first, on top of `d9f4698d`) - -| sha | subject | review item | -|---|---|---| -| `30d72c5b` | `[Fix] (Pipe): refuse a stamp of 0 as fresh on both branches - a read before the first fill is Fatal{UnmigratedPipeInput, "@"}, not default storage` | **M1** | -| `d0ff6475` | `[Fix] (Pipe): re-arm the verify comparator when any of its three knobs changes, not only when PipeVerify does` | minor 2 | -| `12b57055` | `[Docs] (Pipe): record why the forwarders carry no poison check, the InHook re-entry guard, and the Index slot no FillPoints.def row can fill` | minors 1, 3, 5 | -| `44ffafb2` | `[Test] (Pipe): pin the pre-fill window - every stamp of 0 is stale at serial 0, a read before any fill aborts naming "", and the FATAL=0 child leaves through std::exit so the teardown summary is observed` | M1, minor 4 | - -`git diff --stat d9f4698d..HEAD`: 6 files, +102/-15 — `scripts/gen_pipe.py` (+6/-2), `MobileGL/MG_Pipe/generated/PipeFilled.inc` (+6/-2, regenerated), `MobileGL/MG_Impl/Pipe/PipeFill.cpp` (+38/-11), `MobileGL/MG_Backend/MGPipe/PipeInputs.h` (+6), `MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp` (+6), `MobileGL/MG_Test/Pipe/PipeInputsTest.cpp` (+40/-2). All inside A's C.5 rows; no B/C/E file, no fixture. One generated file changed and is committed (`gen_pipe --check` clean). No signature B/C/E code against changed: `MGB_CTX`, `MGP_FILL`, the 63 accessors, the knob names and every log line are as in the contract; the only behavioural change visible to a backend is that a read before the first `MGP_FILL` is now the D6 Fatal instead of default storage. - -## What was done per review item - -- **M1 (serial-0 window read as fresh)** — `gen_pipe.py` `gen_filled` now emits, and `PipeFilled.inc` carries: - ``` - const Uint64 gen = state.FilledGen[index]; - if (gen == 0) return false; - return kMGPipeInputFieldSticky[index] || gen == state.CurrentVerbSerial; - ``` - i.e. a stamp of 0 is "never filled" on both branches (the review's `FilledGen != 0 &&` in the non-sticky arm, written once for both). Proof, three layers: (1) the reviewer's own `prefill.cpp` recompiled and relinked against the rebuilt `build-verify` with the same ninja commands (`~/w7/p1-core-v3-prefill/`): `before-first-fill: serial=0 gen[GetLineWidth]=0 fresh=0 live=1`, **rc=134** (SIGABRT), log `Fatal{UnmigratedPipeInput, "GetLineWidth@"}` — both plain and with `Features.PipeVerify = true` before the read (v2 was `fresh=1`, `stored=0 live=7`, rc=0, no log line); (2) `PipeCatalogue.PipeInputFieldsStartUnfilled` asserts every one of the 63 fields (sticky included) stale on a value-initialised `MGPipeFilledState` at serial 0, before its existing serial-1/serial-2 assertions; (3) the fork case `PipeInputsTest.ReadingBeforeAnyFillAbortsNamingNoVerb`: a live context with `SetLineWidth(7)`, no fill in the process (a visible `GTEST_SKIP` if another case of the same process already filled — `gtest_discover_tests` runs each case alone, so it never skips in ctest), the child reads `gPipeInputs.GetLineWidth()`; the parent asserts SIGABRT, exactly `Fatal{UnmigratedPipeInput, "GetLineWidth@"}`, no `PipeVerifyDiffer`, no `verify armed`. In a verify build the child sets `Features.PipeVerify = true` first (the lane's shape). The read hook's arming-at-first-fill is left as is: `MGP_INPUT_CHECK` precedes `MGP_INPUT_VERIFY_READ` in every accessor and a verify build always defines `MOBILEGL_PIPE_POISON` (`PipeInputs.h:21-23`), so a pre-fill read is the poison's Fatal before the hook could matter; a `static_assert(MOBILEGL_PIPE_POISON, ...)` next to the hook in `PipeFill.cpp` pins that dependency so a future change to the POISON definition fails to compile rather than reopening the window. The comment at the serial bump now states the rule. -- **Minor 1 (forwarders carry no `MGP_INPUT_CHECK`)** — behaviour kept (correct per the review); the exception is now declared in the header above the seven forwarders (why: a forward is a live call, `InvalidateCompileEnv` is reached from backend initialisation before any fill and would be `Fatal{...@}` on every start; the sticky stamp is consulted by no accessor and pinned only through `MGPipeInputFieldIsFresh` in tests), and recorded as deviation 19 below and in the integrator notes so P2's tracker does not "fix" it. -- **Minor 2 (`ArmVerify` re-armed only on a `PipeVerify` change)** — fixed rather than only recorded: `VerifyState` gained `CorruptKnob` (the last-parsed `PipeVerifyCorrupt` string) and the latch keys on all three values (`PipeVerify`, `PipeVerifyFatal`, `PipeVerifyCorrupt`). Lane behaviour unchanged (Features loaded before the first fill → one arm per process); cost two Bool compares and one String compare per fill in verify builds only. Deviation 14 is restated below to say exactly this; `878db2c4`'s subject is now true for all three verify knobs. -- **Minor 3 (`GetBufferBindingSlot(Index)`)** — no code change (the null-by-design Index slot and its poison Fatal are D6's rule); the `CopyField` comment now says why no `FillPoints.def` row can fill it (GLContext derives it through the bound VAO's element-buffer slot, `Core.cpp:98-106`) and that no backend reads it today. `MEASUREMENTS.md` is integrator-owned (C.5): the one-line record for both this and `IsCapabilityEnabledIndexed` is under "Records for the integrator" below. -- **Minor 4 (`VerifyFatalOffLogsTheDivergenceAndContinues` never saw the summary)** — the child now leaves through `std::exit(0)` instead of `_exit(0)`, so `VerifyState::~VerifyState` runs in the child, and the parent asserts `MGPipe: verify summary - 2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0` in the log delta. Passes in build-verify (the child's static destructors run against the forked copy of a fake context; stdio was flushed before the fork, so nothing is printed twice). The lane-level `summary=1` is still re-run below. -- **Minor 5 (hook re-reads the whole field; `InHook` guard)** — comment at the site: the per-read cost (`GetRenderStateParameters` re-copies and compares the struct, `GetProgramForDraw` re-joins the link) as a thing to keep in mind when reading the verify lane's wall time, and what `InHook` prevents (a `GetProgramForDraw`-triggered backend re-entry reaching another accessor becomes a plain load, never a second hook, never a report against a half-copied scratch). Recorded for D.3 below. - -## Verification (WSL, `~/w7/p1-core`, HEAD `44ffafb2`; logs `~/w7/p1-core-v3-*.log`, summary `~/w7/p1-core-v3-summary.txt`, build/prefill log `~/w7/p1-core-v3-build.log`) - -Builds: `cmake --build {build-linux,build-push,build-verify} -j 12` after the patch, all rc=0 (`CCACHE_BASEDIR=/home/swung/w7`); the verification pass re-ran them (`ninja: no work to do` × 3). - -VERIFICATION_TABLE - -Not run (unchanged from v1/v2): the 40-trace retrace under verify and the `integration-verify` lane (E's files, D.3 on the integrated tree). Not built per intermediate commit: the split of `PipeFill.cpp` between `30d72c5b` / `d0ff6475` / `12b57055` was checked hunk by hunk (each commit's hunks are self-contained: the static_assert and comments in A, the `CorruptKnob` member and its uses together in B, comments only in C); only the final tree was compiled. - -## Deviations from the brief (with reasons) — additions/restatements to v1's 1-13 and v2's 14-18 - -14. **(restated)** The knob latches key on the value, not "once per process": `ParsePoisonOmissionKnob` on `Features.PipePoisonOmit`; `ArmVerify` on all three of `Features.PipeVerify`, `PipeVerifyFatal`, `PipeVerifyCorrupt` (as of `d0ff6475`; v2 keyed on `PipeVerify` alone and latched the other two). Reason and cost as in v2. -19. **The seven F-class forwarders carry no `MGP_INPUT_CHECK` / `MGP_INPUT_VERIFY_READ`** (D4: "every accessor body"). Reason: a forward is a live call with no stored value; `InvalidateCompileEnv` is reached from backend initialisation (`BackendObject_DirectVulkan.cpp:388-390,786-788`) before any verb fills, where a check would be `Fatal{InvalidateCompileEnv@}` on every start. Consequence: the sticky stamp (`FilledGen = 1` on the first live fill) and the `Sticky → FilledGen != 0` branch are consulted by no accessor; they are pinned only by `PipeCatalogue.PipeInputFieldsStartUnfilled` / `StickyFieldsAreExactlyTheSeven` and `PipeInputsTest.OmittingOneField...` / `EveryVerbFills...` through `MGPipeInputFieldIsFresh` directly. Declared in the header (`12b57055`). -20. **`MGPipeInputFieldIsFresh` refuses a stamp of 0 on the sticky branch too** (D6 words the sticky branch as `FilledGen != 0`, which it still is; the non-sticky arm gained the same guard). No semantic change to the sticky branch. -21. **The read hook still arms at the first fill** (D8: "Arming, once, at the first fill under verify" — unchanged); the pre-fill window is covered by the poison, and `static_assert(MOBILEGL_PIPE_POISON)` in the verify block makes that dependency a build-time fact. The review's alternative (arm inside the hook when `!Parsed`) was not taken: in every build that compiles the hook the poison is on, so it would be dead code. -22. **`PipeInputsTest` has 12 cases** (v2: 11; brief: 5): `ReadingBeforeAnyFillAbortsNamingNoVerb` added. -23. **`VerifyFatalOffLogsTheDivergenceAndContinues`'s child exits via `std::exit(0)`**, the one child in the file that runs static destructors on purpose (to observe the summary line); every other child still `_exit`s. - -## Where the tree contradicted the brief's numbers - -Unchanged from v1/v2 (`GL_Drawing.cpp:906` vs 907; `RenderStateParameters` 65 members not 67; `CapabilityInput` 35 not 37; c5 regenerates 1 file not 7; integration-gpu 868 entries; the `ctest -N` regex needs the padded form). New in this round: none. One brief sentence that the tree did not honour before this round and does now: D6's "starts at 1 so `FilledGen == 0` means never filled" — true of the stamp values since c2, true of the read predicate only since `30d72c5b`. - -## Records for the integrator (not A's code) - -- **`MEASUREMENTS.md` one-liner for the two accessors where "add the row" does not apply**: `GetBufferBindingSlot(BufferTarget::Index)` (polymorphic on `GLContext` — the bound VAO's element-buffer slot, `Core.cpp:98-106`; null in the block by design) and `IsCapabilityEnabledIndexed(cap ∉ {Blend, ScissorTest})` (the block carries only those two indexed caps) are push-arm `Fatal{UnmigratedPipeInput, ...}` that no `FillPoints.def` row can repair; both unreachable today (every backend slot read is DrawIndirect/DispatchIndirect/Parameter/PixelPack; `VulkanRenderer.cpp` asks `Blend` only). A future backend read of either adds a field, not a row. -- **D.3 gains a failure mode** (the review's section 5, now real): any backend read reached from EGL/init before the first verb is `Fatal{UnmigratedPipeInput, "@"}` in the verify lane. Per E's risk table the answer is a fill point at that boundary (an EGL virtual), never a sticky flag or a `FillPoints.def` row. The scout catalogue finds no such read on this tree (the two out-of-verb pulls are the forwarded, unchecked `InvalidateCompileEnv` calls), so no lane is expected to trip today. -- **D.3 wall-time reading**: the compare-at-read hook re-reads and compares the whole field per backend accessor call (`GetRenderStateParameters` = a struct copy + 65-member compare per read; `GetProgramForDraw` = a link join per read); within the 5-10× verify budget by design, but the verify lane's time is not the poison's cost. -- **The forwarders' missing check is deliberate** (deviation 19): P2's tracker should not add one. -- The v2 records stand: the vacuous `-R 'DirectGLES\.Verify\..*Mipmap'` CI regex (grep the lane log for the exact Fatal instead), the `GIT@` stamp refresh at reconfigure. - -## Unfinished - -Nothing in A's scope. M1 is fixed with the three proofs above; minors 1, 3, 5 are recorded in code comments and here (the `MEASUREMENTS.md` line is the integrator's file); minors 2 and 4 are fixed in code and covered by tests. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-review-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-review-v1.md deleted file mode 100644 index f5d39977..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-review-v1.md +++ /dev/null @@ -1,70 +0,0 @@ -# P1 package B — p1-espryt — adversarial review v1 - -Reviewed: tree `~/w7/p1-espryt`, branch `p1/espryt`, HEAD `f4ea84c5` (one commit on top of `p1/contract` = `bf86b1ed`). Implementer's result: `p1-results/espryt-v1.md`. Brief: `BRIEF-P1.md` A, B (D1–D13), C.2, C.5, D, E. - -Stance: I tried to refute correctness, completeness and the G1/G2/G7 gate claims. Every number below was re-derived by me; logs under `~/w7/p1-espryt-review-*` and `~/w7/p1-espryt-review-int-*`; scripts under `scratchpad/wf4/espryt-review/`. The tree was left exactly as found (`git status`: only `build-linux/`, `build-push/` untracked; `git diff --stat` empty). The one throwaway worktree I created (`~/w7/p1-espryt-review-int`, branch `tmp/p1-espryt-review-int`) was removed afterwards. - -## Verdict - -**Approved — zero majors.** The package is a textually pure conversion of exactly the 122 lines the brief assigns to it, in exactly the four files it owns, and the pull build is symbol- and size-identical to the `087685d1` baseline. The one deviation from D9 (the hoisted `auto& live = *MGB_CTX;` in the fb-slot cache refill) is declared, justified with a bisect, semantically equivalent in both arms, and is the shape that yields `.text +0` where the literal D9 text does not. - -## What I re-ran (commands + observed output) - -| check | command (in `~/w7/p1-espryt`) | observed | -|---|---|---| -| diff scope | `git diff --stat p1/contract..HEAD` | 4 files, +129/−124: `DirectGLES.cpp` 190, `Managers.cpp` 47, `MultiDraw.cpp` 11, `Utils.cpp` 5 — nothing outside C.5's B row; no fixture touched | -| textual purity of every changed line | `scratchpad/wf4/espryt-review/diffcheck.py` over `git diff p1/contract..HEAD` (pairs `-`/`+` lines per hunk; a pair is "pure" iff `+` with `MGB_CTX->`→`MG_State::pGLContext->` equals `-`) | 86 hunks, **112 pure arrow substitutions**; the only non-pure pairs are the 4 include insertions, the `:143` decltype line (an arrow inside `decltype`, pure by the same rule), the 143–155 block, and the 8 Managers.cpp D9 lines (`if (MG_State::pGLContext)`→`if (MGB_CTX_LIVE)` ×3, `&& MG_State::pGLContext &&`→`&& MGB_CTX_LIVE &&`, `if (MG_State::pGLContext && `→`if (MGB_CTX_LIVE && `, `!= nullptr`→`MGB_CTX_LIVE` ×3). No accessor renamed, no argument changed, no evaluation order moved, no `const&` binding lost. | -| the touched old-line set equals the brief's site list | `scratchpad/wf4/espryt-review/linecheck.py` (old-side line numbers of every `-` line vs C.2's table + D9's 144/147/150) | `DirectGLES.cpp` removed 94 = brief 94, `Managers.cpp` 23 = 23, `MultiDraw.cpp` 5 = 5, `Utils.cpp` 2 = 2; `removed-not-in-brief []` and `brief-not-removed []` for all four | -| G2 (this package's share) | `grep -rc "pGLContext" MobileGL/MG_Backend/DirectGLES \| grep -v ":0$"` | empty (all 10 files 0) | -| no site outside ownership touched | `grep -rc "pGLContext" MobileGL/MG_Backend \| sort \| diff ~/w7/p1-before-pglcontext.txt -` | only the four DirectGLES lines change (92/23/5/2 → 0) plus the two contract-created `MGPipe/PipeInputs.{h,cpp}:0` rows; every DirectVulkan count identical to the baseline | -| MGB_CTX counts | per-file `grep -o` | arrows 91/15/5/2 (brief: 91 15 5 2); `MGB_CTX` tokens DirectGLES.cpp 93 (= 91 + `*MGB_CTX` + `MGB_CTX_IDENTITY`), `MGB_CTX_LIVE` Managers.cpp 8 (brief: 8), `MGB_CTX_IDENTITY` 1 | -| include placement (D3) | `grep -n` | `DirectGLES.cpp:18` Core.h → `:19` switch; `MultiDraw.cpp:11`→`:12`; `Utils.cpp:19`→`:20`; `Managers.cpp:9` `"Managers.h"` → `:10` switch (Managers.cpp does not include Core.h directly, per D3) | -| CRLF | `grep -c $'\r'` ×4 | 0 0 0 0 | -| gen_pipe | `python3 scripts/gen_pipe.py --check` | rc=0, "generated files are up to date" (63 fields, 7 sticky, 69 verbs, 9 classes) | -| pull build | `cmake --build build-linux -j 12` | rc=0 | -| **G1** | `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 --json … --markdown …` | `.text 10792579 -> 10792579 (+0)`, `.data/.bss/.rodata +0`, `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed`, `27060 normalised names, 27060 unchanged` | -| nm set | `nm --defined-only … \| awk '{print $NF}' \| sort` before/after, `diff` | 0 lines | -| raw `.text` bytes | `objcopy -O binary --only-section=.text` ×2, `cmp -l` | sizes equal; **39 bytes differ**, deltas +1 (23 bytes) and +2 (16 bytes); mapped through `nm -S` to exactly the 6 functions the implementer names (`SyncMipmapsToBackend` 9, `SyncBuiltinSamplerToBackend` 8, `CopyTexSubImage2D` 7, `SyncTextureParamsToBackend` 6, `CopyTexImage2D` 5, `GetTexImage` 4) — the `__LINE__` immediates in `ErrorInfo` constructions shifted by the inserted include (+1) and, after `:148`, the hoisted line (+2). Sizes unchanged, so G1 as decoded ("`.text` byte delta of zero" = size) holds; see minor 1 for the wording | -| push build | `cmake --build build-push -j 12` | rc=0; `nm -C …/DirectGLES.cpp.o`: `gPipeInputs` refs 2, `pGLContext` 0 | -| **push arm at DEBUG level** (never built by anyone: both INFO builds compile out `DirectGLES.cpp:4200-…`, which holds the converted site at `:4217`) | `scratchpad/wf4/espryt-review/10_debugpush.sh`: the four TUs' `build-push/compile_commands.json` commands with `-DMOBILEGL_LOG_ACTIVE_LEVEL=MOBILEGL_LOG_LEVEL_DEBUG` and `-fsyntax-only` (this also arms `MOBILEGL_PIPE_POISON` and the `MGP_INPUT_CHECK` bodies) | `DirectGLES.cpp rc=0 errors=0`, `Managers.cpp 0/0`, `MultiDraw.cpp 0/0`, `Utils.cpp 0/0` (twice each: static + shared targets) | -| G7 names | width-tolerant regex `grep -E '^\s*Test\s+#[0-9]+:'` | 2334 = 2334, `comm -23` empty, `comm -13` empty. The brief's own regex `'^\s+Test #'` yields **1335** on this build (confirmed: ctest pads to `Test #1:` above 999 tests) — the implementer's "tree vs brief" item 2 is correct and the integrator's D.1 step needs the tolerant form | -| unit, pull | `ctest --test-dir build-linux -L unit --no-tests=error -j 8` | rc=0, 1466 (3 skipped by design) | -| unit, push (contract tree) | `ctest --test-dir build-push -L unit --no-tests=error -j 8` | rc=8, 18 failed / 1466 — the same 18 the implementer lists (contract filler copies nothing; expected per C.2 step 4) | -| integration lane, pull | `ctest --test-dir build-linux -L integration-gpu -R '^DirectGLES\.' --no-tests=error -j 4` | **441/441 passed, rc=0**. The implementer's run had 2 failures (`UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn`, `PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn`); on the identical binary they pass here, which is consistent with the "shared per-lane log file under `-j 4`" flake they diagnosed (`UnlocatedIoBlockScenario.cpp:362-371`: the case trusts bytes after `LibraryLogSize()` in a file every process of the lane appends to). Pre-existing, not this package's | -| D10 / PipeStats constraints | `git diff -U0 p1/contract..HEAD \| grep -c "SyncPersistentMappedRange\|SyncGpuWrites"` / `\| grep -c AddCalls` | 0 / 0 | -| commit hygiene | `git log -1 --format=%B`; `git branch -r \| grep p1/` | subject exactly the C.2 step-5 text, blank line, four `- ` bullets; no attribution lines; no remote branch (not pushed) | - -## Integrated-tree check (throwaway, `p1/core` HEAD `83b16561` = c1…c5 incl. the 83 fill points + `f4ea84c5` cherry-picked as `4c92f727`) - -INTEGRATED_SECTION_PLACEHOLDER - -## Deviations from the D-decisions, audited - -1. **D9 `:150` shape** — declared. Committed: `auto& live = *MGB_CTX;` once, then `&live.GetFramebufferBindingSlot(i)` in the loop. Pull arm: `*MG_State::pGLContext` is `UniquePtr::operator*` → `GLContext&`, the same object the old `ctx` pointed at; behaviour when no context is live is identical to before (both the old `ctx->…` on a null `get()` and the new `*pGLContext` on a null pointer are only reached when the cached identity differs, i.e. never in that state). Push arm: `*(&gPipeInputs)` → `PipeInputs&`, whose `GetFramebufferBindingSlot` (`PipeInputs.h:494-500`) returns the live slot reference the filler stored; the identity key stays `MGB_CTX_IDENTITY` = `gPipeInputs.ContextIdentity()` so a recreated context still invalidates. I verified the committed shape is `.text +0` (above); I did **not** independently rebuild the literal D9 variant — the implementer's `~/w7/p1-espryt-bisect.txt` (v4/v5: +32, `SyncCurrentProgram` 1494→1510, `ForceBindCurrentFBO` 181→190, `BlitNamedFramebuffer` 592→593; v8 = committed: 189968 = baseline) is internally consistent and the TBAA explanation is the standard one for a pointer store next to a `unique_ptr` load. The deviation is the better reading of D9's own "if `.text` moves, this is the first suspect". -2. **`:143` keeps `std::remove_reference_t<…>`** — D9 abbreviated it; the alias must be the slot type, not a reference. Correct. -3. **Push lane not demonstrated in the package's own tree** — correct at the time (c3 had not landed); now demonstrated by me on the integrated throwaway tree, see above. - -Undeclared deviations found: **none**. - -## Majors - -None. - -## Minors - -1. **Wording "pull build byte-identical" (commit subject, brief-dictated) vs. 39 differing `.text` bytes.** G1 is size/symbol based and holds (0/0/0/0, `.text +0`), and the body of the commit discloses the `__LINE__` immediates, but the integrator's `MEASUREMENTS.md` P1 attribution line ("expected: 0 bytes; 58 lines converted, none code-bearing", brief D.5) should say "sizes identical; 39 bytes of `__LINE__` immediates in 6 `DirectGLES.cpp` functions from the inserted include (+1) and the hoisted cache line (+2)" rather than "0 bytes". Integrator doc item, not a code change. -2. **Push-build unit tests that drive backend helpers directly** (`DirectGLESSanity.*`, `DirectGLESTextureSync.*`, `FramebufferTest.*`, `TextureTest.*` families, 18 on the contract tree) read `gPipeInputs` with no verb ever filled — they bypass `GLFunctionsTable` and therefore every `MGP_FILL`. Whatever their state on the integrated tree (recorded in the section above), this is a design consequence of D7's placement rule, not of this package's textual conversion; the implementer flagged it for A/integrator (D.2 expects "unit + integration-gpu green" under push). Recording so it is tracked. -3. **Brief D.1 / C `ctest -N` regex** (`'^\s+Test #'`) undercounts to 1335 on a 2334-test build; the implementer's tolerant form (`'^\s*Test\s+#[0-9]+:'`) is the one the integrator must use, or `comm -23` reports 999 phantom losses. -4. The two `ActuallyArmedWhenTheEnvironmentPinsItOn` itests are a `-j 4` flake in package E's file set (`MG_IntegrationTest/**`); the ambient lanes share one `MOBILEGL_LOG_FILE_PATH` per lane. Not this package's; noted for E/integrator. - -## Where the tree contradicted the brief - -- Only the `ctest -N` regex (minor 3). Every site count, line number and per-file total in C.2 matched the tree exactly (line check above). - -## Hunted for and not found - -- A converted site with a different accessor / dropped null check / moved evaluation / lost `const&`: none (112 pure substitutions + 1 decltype; the only `GetRenderStateParameters()` binding that is not `const auto&` is `DirectGLES.cpp:4158`'s `const FloatVec4& cc = …ClearColor`, a member reference — no 1168-byte copy). -- A site left on `pGLContext` behind a macro: the four files have 0 tokens; the one converted site under a preprocessor conditional (`DirectGLES.cpp:4217`, `LOG_LEVEL_DEBUG` block) compiles under the push arm at DEBUG. -- A site touched in a shared or unowned file: none (diff is the four owned files). -- Reformatting beyond touched lines: none (diff = 122 site lines + 4 includes + 1 hoisted line + the `:150` `&ctx->`→`&live.` line). -- A `PipeStats` literal or D10 sync site on a changed line: none. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-v1.md deleted file mode 100644 index beccda8a..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/espryt-v1.md +++ /dev/null @@ -1,79 +0,0 @@ -# P1 package B — p1-espryt — result v1 - -Tree `~/w7/p1-espryt`, branch `p1/espryt`, branched from tag `p1/contract` (`bf86b1ed`). Working tree clean after the commit. Nothing pushed. - -## Commits (p1/contract..p1/espryt) - -| hash | subject | -|---|---| -| `f4ea84c5f786ff556e8c41508072ad3782bee3ca` | `[Refactor] (Espryt): route every frontend read through MGB_CTX - 113 arrow sites sed'd, 9 non-arrow lines converted (the fb-slot cache keys on MGB_CTX_IDENTITY); pull build byte-identical` | - -Files: `MobileGL/MG_Backend/DirectGLES/{DirectGLES.cpp, Managers.cpp, MultiDraw.cpp, Utils.cpp}` only (4 files, +129/−124). No other file touched; no fixture binary touched. - -## What was done (brief C.2 steps 1–5) - -1. `#include ` inserted per D3: `DirectGLES.cpp` after its `Core.h` include (old :18 → new :19), `MultiDraw.cpp` after :11 (→ :12), `Utils.cpp` after :19 (→ :20), `Managers.cpp` after its `Managers.h` include (old :9 → new :10). Includes were inserted after all line-number-addressed edits, so every brief line number was applied against the unshifted file. -2. Mechanical `MG_State::pGLContext->` → `MGB_CTX->` over the four files: 91 + 15 + 5 + 2 = **113 occurrences on 113 lines**, matching the brief's per-file arrow table exactly (verified line-for-line before the sed: DirectGLES.cpp 143…9510, Managers.cpp 3645…8751, MultiDraw.cpp 45,51,52,87,95, Utils.cpp 2297,2301). -3. The 9 non-arrow lines per D9: `Managers.cpp` 3644, 3844, 8750 (`if (MG_State::pGLContext) {` → `if (MGB_CTX_LIVE) {`), 3773 (`&& MG_State::pGLContext &&` → `&& MGB_CTX_LIVE &&`), 4735 (`if (MG_State::pGLContext && ` → `if (MGB_CTX_LIVE && `), 7192/7200/7203 (`MG_State::pGLContext != nullptr` → `MGB_CTX_LIVE`; their `?` arms 7193/7201/7204 took the sed); `DirectGLES.cpp` 143–155 block: `:144` → `static const void* g_fbSlotCacheContext = nullptr;`, `:147` → `const void* ctx = MGB_CTX_IDENTITY;`, `:150` → see deviation 1. -4. Pull build, symbol report, push build, lanes — below. -5. One commit, message per brief plus `- ` bullets. - -## Verification (WSL, `~/w7/p1-espryt`; logs under `~/w7/p1-espryt-*`) - -| command | result | -|---|---| -| `grep -rc "pGLContext" MobileGL/MG_Backend/DirectGLES \| grep -v ":0$"` | **empty** (all 10 files report 0) | -| `for f in …; do grep -o 'MGB_CTX->' … \| wc -l; done` | DirectGLES.cpp **91**, Managers.cpp **15**, MultiDraw.cpp **5**, Utils.cpp **2** (the brief's "91 15 5 2"; `MGB_CTX` tokens total in DirectGLES.cpp = 93 = 91 arrows + `*MGB_CTX` in the cache refill + `MGB_CTX_IDENTITY`) | -| `grep -c "MGB_CTX_LIVE" MobileGL/MG_Backend/DirectGLES/Managers.cpp` | **8** | -| `grep -c "MGB_CTX_IDENTITY" MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp` | 1 | -| `cmake --build build-linux -j 12` | rc=0 (`[49/49] Linking … MobileGLIntegrationTest`) | -| `python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 --json … --markdown ~/w7/p1-espryt-symbol.md` | `.text 10792579 -> 10792579 (+0)`, `.data/.bss/.rodata +0`, `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed`, `27060 normalised names, 27060 unchanged` — **G1: 0/0/0/0, .text +0** | -| `nm --defined-only` name set, before vs after | diff **empty** (27799 = 27799) | -| raw `.text` byte compare (`objcopy -O binary --only-section=.text` + `cmp -l`) | 39 bytes differ in 6 DirectGLES.cpp functions (`SyncMipmapsToBackend`, `SyncBuiltinSamplerToBackend`, `SyncTextureParamsToBackend`, `CopyTexSubImage2D`, `CopyTexImage2D`, `GetTexImage`), function sizes unchanged. Disassembly diff: every difference is a `movl $0xNNN,0x8(%rax)` immediate = a `__LINE__` value stored into an `ErrorInfo` (e.g. `0xf0e`→`0xf0f` = 3854→3855): 23 bytes are +1 (lines after the inserted include), 16 are +2 (DirectGLES.cpp lines after the include *and* the hoisted `auto& live` line). The object built with **only** the include already shows these; the seds and the D9 rewrites add no byte. Not a gate item (G1 is symbol/size based), recorded for completeness. | -| `ctest --test-dir build-linux -L unit --no-tests=error --output-on-failure -j 8` | rc=0, **100% tests passed, 0 failed out of 1466** (on the final `.so`) | -| `ctest --test-dir build-linux -L integration-gpu -R '^DirectGLES\.' --no-tests=error --output-on-failure -j 4` | 441 tests; **439 passed, 2 failed**: `DirectGLES.UnlocatedIoBlocks.UnlocatedIoBlockScenario.TheEmulationIsActuallyArmedWhenTheEnvironmentPinsItOn`, `DirectGLES.PointSizeDemotion.PointSizeDemotionScenario.TheDemotionIsActuallyArmedWhenTheEnvironmentPinsItOn` — both are "text appended to the lane's `MOBILEGL_LOG_FILE_PATH` log after the case started" assertions (`UnlocatedIoBlockScenario.cpp:516`, `PointSizeDemotionScenario.cpp:512`). Re-run of the 10 `ActuallyArmedWhenTheEnvironmentPinsItOn` cases: `-j 1` **10/10 pass**, `-j 4` **10/10 pass**. The same two cases also failed identically in the push lane of a tree that did **not** contain this commit (bare `p1/core` c2, see below), and the pull `.so` is symbol- and size-identical to the baseline, so this is a pre-existing parallel-lane flake of the shared per-lane log file, not a regression of this package. Lane label used: `integration-gpu` (the tree has only `integration-gpu` and `unit`; the `-R '^DirectGLES\.'` selects the DirectGLES entries). | -| `ctest -N` name diff vs `~/w7/p1-before-ctest-names.txt` | `comm -23` **empty**, `comm -13` **empty**, 2334 = 2334 — see "tree vs brief" item 2 for the regex. | -| `cmake --build build-push -j 12` (`MOBILEGL_PIPE_PUSH=ON`) | rc=0 (`[55/55] Linking …`); the DirectGLES.cpp object references `gPipeInputs` and no `pGLContext` (`nm`: 0 / 2) | -| `ctest --test-dir build-push -L unit` on the **contract** tree | 18 failed / 1466 (expected on the contract tree: the c1 filler copies nothing, C.2 step 4 note) | -| `ctest --test-dir build-push -L integration-gpu -R '^DirectGLES\..*ClearThenReadPixels'` on the contract tree | 0/5 pass, as the brief predicts for the contract tree | -| D10: `git diff -U0 … \| grep -c "SyncPersistentMappedRange\|SyncGpuWrites"` on changed lines | **0** — the 8 SPMR + 3 SGW Espryt sites are untouched (they are not on any converted line) | -| `git diff -U0 … \| grep -c AddCalls` on changed lines | 0 — `PipeStats` literals untouched | -| CRLF check on the four files | none | - -### Push-build proof against the real filler (throwaway, removed afterwards) - -Because `p1/core` had progressed to c2 (`3aa4d8af`, the per-class filler) while this package ran, `p1/espryt` was cherry-picked onto `p1/core@3aa4d8af` in a throwaway worktree (`~/w7/p1-espryt-onto-core`, branch `tmp/p1-espryt-onto-core`; **both deleted after the run**, logs kept as `~/w7/p1-espryt-onto-core-*.log`): - -- cherry-pick applied cleanly (disjoint files); `cmake --build build-push` rc=0. -- `ctest -L unit` (push): 18 failed / 1466 — the same 18 as on the contract tree: `DirectGLESSanity.{BindsAMultisampleTextureDespiteTheDefaultMipmapFilter, BindingZeroClearsPreviousNativeTextureBinding}`, `NegativeApiErrorsTest.MultiBindTextures{RejectsBadRangesAndNames, BindsToTheTexturesOwnTarget}` (aborted), `FramebufferTest.{DrawIntoAWidenedDrawBuffer…, ClearIntoAWidenedDrawBuffer…, ApplicationAlphaMaskOff…, DualSourceBlendFactorsReachTheDriver…, DualSourceBlendIsDeclined…, DualSourceFactorsAreDeclinedEvenWithBlendingDisabled, DualSourceFactorsWithBlendingDisabledStillReachACapableDriver}`, `TextureTest.StorePackedWordsToClientCopiesWordsVerbatimUnderPackParams` (aborted), + 6 more of the same families (full list in `~/w7/p1-espryt-onto-core-unit.log`). -- `ctest -L integration-gpu -R '^DirectGLES\.'` (push): **68 passed / 373 failed** (SegFault or `Subprocess aborted`). A single case run with `MOBILEGL_LOG_FILE_PATH` shows the reason: `MGPipe: Fatal{UnmigratedPipeInput, "GetImageTextureBinding@"}` — the verb name is ``, i.e. **no verb was ever filled in the process**. Cause: `p1/core` at c2 (and at its current head `275dd3ed`, c4) carries **0** `MGP_FILL(` statements in `MobileGL/MG_Impl/GLImpl` (c3, the 83 fill points, has not landed yet). So the push lane cannot be green on any tree yet; this is not a package-B defect and not a fill-table gap (those would name a real verb). **The brief's "rerun after rebasing onto p1/core c2+ and it must be green" has to be re-run by the integrator once c3 is on `p1/core`** (D.2). -- The 18 push-build unit failures are the same phenomenon in the unit binaries (backend helpers driven directly by the tests, never through a fill point); with c3 landed most should turn green, but tests that call backend entry points without going through `GLFunctionsTable` will still read `@` and are an A/integrator item (recorded here so it is not a surprise in D.2). - -## Deviations from the brief (with reason) - -1. **D9 line 150 (`DirectGLES.cpp` cache refill loop).** D9 spells `g_fbSlotCache[i] = &MGB_CTX->GetFramebufferBindingSlot(static_cast(i));`. Built that way the pull build is **not** byte-identical: `.text +32`, 3 resized symbols (`PrgramImpl::SyncCurrentProgram` 1494→1510, `ForceBindCurrentFBO` 181→190, `BlitNamedFramebuffer` 592→593) — exactly the "first suspect" D9 names. Bisected by rebuilding only the DirectGLES.cpp object per cumulative variant (`~/w7/p1-espryt-bisect.txt`): include alone 0, +91 arrow seds 0, +`const void*` cache (144) 0, **+line 150 via `MGB_CTX->` = +32**, +line 147 `MGB_CTX_IDENTITY` 0. Reason: `g_fbSlotCache[i] = …` stores a pointer, and under clang's TBAA any pointer store may alias the `unique_ptr`'s own pointer, so `MGB_CTX->` inside the loop re-loads `pGLContext` per iteration (the original read through the local `ctx`). Committed shape hoists the dereference once: - ```cpp - const void* ctx = MGB_CTX_IDENTITY; - if (ctx != g_fbSlotCacheContext) { - auto& live = *MGB_CTX; - for (SizeT i = 0; i < g_fbSlotCache.size(); ++i) { - g_fbSlotCache[i] = &live.GetFramebufferBindingSlot(static_cast(i)); - } - g_fbSlotCacheContext = ctx; - } - ``` - Measured: `.text +0`, 0/0/0/0. Semantics identical in both arms (pull: `GLContext&`; push: `PipeInputs&`, the identity compare still uses `MGB_CTX_IDENTITY`, so a recreated context invalidates the cache under push too). One extra line inside the touched 143–155 block; no `pGLContext` token. If the integrator prefers the literal D9 text, the cost is the attributed +32 above (both variants' numbers are in the bisect file) — the "no reformatting beyond the touched lines" constraint is respected either way. -2. **Line 143** keeps `std::remove_reference_tGetFramebufferBindingSlot(FramebufferTarget::Draw))>` (D9 abbreviates it as `decltype(MGB_CTX->…)`); it is the sed result and the `remove_reference_t` is required for the alias to be the slot type. -3. **Push-lane green not demonstrated** (see above): impossible on any tree until c3 lands; the brief's step-4 expectation of a `Fatal{UnmigratedPipeInput}` on the contract tree was reproduced instead. - -## Where the tree contradicted the brief's numbers - -1. None for the site counts: DirectGLES.cpp 92 lines / 91 arrows, Managers.cpp 23 / 15, MultiDraw.cpp 5 / 5, Utils.cpp 2 / 2, 9 non-arrow lines at exactly the listed line numbers; `~/w7/p1-before-pglcontext.txt` per-file numbers match. -2. **Baseline `ctest -N` name file vs the brief's own extraction regex.** `~/w7/p1-before-ctest-names.txt` holds 2334 names; the brief's pipeline `grep -E '^\s+Test #'` applied to the same build lists only 1335 (both in `~/w7/pipe/build-linux` today and in this tree) because with 2334 tests ctest pads the number to four columns (`Test #1:` … `Test #1000:`), so the single-space regex matches only #1000–#2334. Width-tolerant form used here: `ctest --test-dir build-linux -N | grep -E '^\s*Test\s+#[0-9]+:' | sed -E 's/^\s*Test\s+#[0-9]+: //' | sort` → 2334 names, `comm` both ways empty. The integrator's D.1 `comm -23` step needs the same regex or it reports 999 "lost" names (all of `TextureTest.*`, `ProgramUtilTest.*`, … i.e. tests #1–#999). -3. `SyncCurrentProgram` (the symbol the literal D9 line 150 grows) lives in `DirectGLES.cpp` (`PrgramImpl::SyncCurrentProgram`), not `Managers.cpp`; the brief's `Managers.cpp:7193-7204` reference in D7 is `AttachPassthroughTessControlStage`'s ternaries (converted per D9, byte-neutral). -4. `p1/core` moved during this package: c2 `3aa4d8af`, then c4 `275dd3ed` (comparator) — c3 (83 fill points) not yet present at the time of writing. - -## Unfinished - -- Push-lane green (`ctest --test-dir build-push -L integration-gpu -R '^DirectGLES\.'`) — blocked on `p1/core` c3; re-run at integration (D.2). Everything else in the package's verification list is done. -- The two `ActuallyArmedWhenTheEnvironmentPinsItOn` itests are a pre-existing `-j 4` flake (pass serially and in isolation, fail identically without this commit); not fixed here (`MG_IntegrationTest/**` is package E's file set). diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-review-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-review-v1.md deleted file mode 100644 index 77ff30e7..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-review-v1.md +++ /dev/null @@ -1,260 +0,0 @@ -# Adversarial review — package `fix-core` (P1 lane findings F1 + F2) - -Reviewer ran everything independently in WSL `~/w7/p1-fix-core` (branch `p1/fix-core`, HEAD -`9bd6d394`, three commits on top of `feat/disaggregated@97b997d5`). Tree left exactly as found -(`git status --porcelain` clean apart from the three untracked build directories, HEAD unchanged). - -**Verdict: approved — 0 majors, 7 minors.** Every claim in `fix-core-v1.md` reproduced. The -central attack ("zero divergences was bought by disabling the read hook / by silencing the -comparator") is refuted by direct experiment below. - ---- - -## 1. What was re-run, and what came back - -Diff under review (`git diff 97b997d5..HEAD`, 371 lines, 6 files, nothing under `MG_Backend/`, -nothing under `tools/trace_replay/fixtures/`): - -``` -MobileGL/MG_Impl/Pipe/PipeFill.cpp | 28 ++ -MobileGL/MG_Pipe/FillPoints.def | 31 +- -MobileGL/MG_Pipe/PipeMutation.h | 42 ++ (new) -MobileGL/MG_Pipe/generated/PipeFillPoints.inc | 12 +- -MobileGL/MG_State/GLState/TextureState/TextureState.h | 31 +- -MobileGL/MG_Test/Pipe/PipeInputsTest.cpp | 105 ++ -``` - -| check | command | observed | -|---|---|---| -| G1 pull build | `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/.bss/.rodata +0`, `27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed`; file size identical (19100448 both sides) | -| generator | `python3 scripts/gen_pipe.py --check` | `71 calls, 69 verify payloads, 63 PipeInputs fields (7 sticky), 69 verbs, 9 classes`, `0 UNMAPPED`, `generated files are up to date`; `git status` clean afterwards | -| generator self-test | `python3 scripts/gen_pipe.py --self-test` | `6 negative-control trip(s), positive control OK` | -| unit, pull | `ctest --test-dir build-linux -L unit --no-tests=error -j 8` | `100% tests passed, 0 tests failed out of 1485` | -| unit, push | `ctest --test-dir build-push -L unit ...` | `11 failed out of 1485` — exactly the F3 list owned by `p1/fix-tests` (`DirectGLESSanity` ×2, `DirectGLESTextureSync` ×1, `FramebufferTest` ×7, `TextureTest` ×1) | -| unit, verify | `ctest --test-dir build-verify -L unit ...` | same 11, same names | -| `ctest -N` | `comm` vs `~/w7/p1-before-ctest-names.txt` | 0 removed, 29 added; vs `~/w7/pipe/build-linux` names: 0 removed, **exactly 3 added**, all `PipeInputsTest.*` from this package. `build-linux` and `build-push` name lists are identical (`diff` empty) | -| integration-verify | `ctest --test-dir build-verify -L integration-verify --no-tests=error -j 4` | `rc=0`, `100% tests passed, 0 tests failed out of 818`, `grep -c "Fatal{UnmigratedPipeInput"` = 0, `grep -c "Fatal{PipeVerifyDiffer"` = 0. All eight formerly-aborting `DirectVulkan.Verify.{UnboundImageDescriptorScenario,SampledSetStalenessScenario}.*` entries pass | -| the four controls | `ctest --test-dir build-verify -R 'PoisonOmitted\.\|VerifyCorrupted\.'` | 4/4 pass (`DirectGLES/DirectVulkan.VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported`, `...PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb`) | -| 79-case retrace | `MOBILEGL_PIPE_VERIFY=1 python3 ~/w7/retrace_gate.py --tree ~/w7/p1-fix-core --lib .../build-verify/libMobileGL.so --out ~/w7/fcr-retrace -j 4` | `passed 79 / 79; failed: []` | -| retrace arming | in `~/w7/fcr-retrace`: `ls *.log` = 79; `grep -l "armed, zero divergences, zero unmigrated reads" *.log` = **79**; `grep -l "Fatal{" *.log` = 0; `grep -rn "UnmigratedPipeInput\|PipeVerifyDiffer" .` = nothing | -| include closure | `python3 scripts/check_include_closure.py` | `3 probes, 0 skipped, 0 problem(s)` | - -The arming assertion is not decorative: `tools/trace_replay/run_trace_case.cmake:164` does -`string(FIND "${pipe_verify_log}" "MGPipe: verify armed" ...)` and `:174` greps the case log for -`Fatal\{(PipeVerifyDiffer|UnmigratedPipeInput|PipeVerifyBadKnob)`. Spot-checked a case log -directly: `improved-transparency-minecraft-26.3/DirectGLES/output/mobilegl.log:329` carries -`MGPipe: verify armed - 63 fields, 69 verbs, fatal=1`. - -Note the `build-push` incremental build had 50 targets to relink when I started -(`[50/50] Linking ... MobileGLIntegrationTest`), i.e. the implementer's `build-push` was one step -stale when their table (c) was produced. Re-running after a full rebuild reproduces their numbers -exactly, so the claim stands. - ---- - -## 2. The attacks that were tried and failed - -### 2a. "Zero divergences was achieved by disabling the read hook" - -Refuted three ways. - -1. `MobileGL/MG_Impl/Pipe/PipeFill.cpp` gains **only** the new `MGPipeNoteFrontendMutation` - (+ one `#include`). `MGPipeVerifyReadHook` (`PipeFill.cpp:439-462`) is byte-identical to - `97b997d5`: it still re-reads the whole field out of the live context - (`MGPipeFillAccess::CopyField(g_readScratch, *ctx, field)`) and still calls - `ReportDivergence(field, "read")`. -2. Every `integration-verify` entry still carries `MOBILEGL_PIPE_VERIFY=1` in its ctest - `ENVIRONMENT` property (verified in - `build-verify/MobileGL/MG_IntegrationTest/MobileGLIntegrationTest[17]_tests.cmake`), so the - comparator is armed for all 818. -3. **Direct falsification.** I inserted `return;` as the first statement of - `MGPipeNoteFrontendMutation` (`PipeFill.cpp:479`), rebuilt `build-verify`, and re-ran: - ``` - ctest --test-dir build-verify -L integration-verify \ - -R 'UnboundImageDescriptorScenario|SampledSetStalenessScenario' -j 4 - -> 8 tests failed out of 30, all Subprocess aborted: - DirectVulkan.Verify.SampledSetStalenessScenario.{AFilterChange,ASamplerObject}CompletesTheTexture - DirectVulkan.Verify.UnboundImageDescriptorScenario.* (6) - ctest --test-dir build-verify -R 'PipeInputsTest\.(AFrontendMutation|TheMutationNotice)' - -> 2 of 3 fail; child log carries - MGPipe: Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays", verb=1, where=read} - ``` - That is the lane failure from `P1-LANE-FINDINGS.md` §F2, reproduced exactly, and it proves the - read hook is live and that the notice is the thing carrying the fix. The edit was reverted - (`git checkout --`), `build-verify` rebuilt, and the three tests are green again; - `git status --porcelain` is clean. - -### 2b. "The F2 mechanism silences the comparator / makes it compare a value against itself" - -The notice refreshes the pushed **value at the point of the frontend write**, not at read time. -The read hook still compares the stored value against a fresh live read at every accessor call, so -any mutation path added later without a notice still reds the lane — demonstrated by 2a.3. No -compare is skipped, no field is excluded, `kMGPipeInputFieldSticky[]` is untouched, and the poison -stamp is deliberately not written (`PipeFill.cpp:479-491` copies the value only), so negative -control B and `Fatal{UnmigratedPipeInput}` both survive — confirmed by the four control entries -passing and by `PipeInputsTest.TheMutationNoticeRefreshesTheValueButNotTheStamp`. - -This is the findings' explicitly preferred option, so it is not an undeclared deviation. - -### 2c. "The enumeration missed fields" — re-derived from the code, independently - -The result file's Step 1–3 was not taken on trust. Re-derivation: - -* **Backends cannot reach any `GLContext` mutator.** Gate G2 holds (`grep -rc pGLContext - MobileGL/MG_Backend` is empty on the integrated tree), so the only context-level calls a backend - can make are the `PipeInputs` accessors, and the only non-const ones there are the three - forwarders `RecordError`, `InvalidateCompileEnv`, `ValidateProgramName` - (`MG_Impl/Pipe/PipeFill.cpp:537-551`) — all F-class/sticky with no storage. So a backend can only - move a pushed value *through a frontend object that calls back into the context*. -* **That callback surface is exactly five call sites, on three methods.** - `grep -rn "pGLContext->" MobileGL/MG_State/ | grep -v Core.cpp` yields, excluding comments: - - `MG_State/GLState/SamplerState/SamplerObject.cpp:34` → `BumpSamplingResolutionGeneration()` - - `MG_State/GLState/TextureState/TextureObject.cpp:37` → `BumpSamplingResolutionGeneration()` - - `MG_State/GLState/TextureState/TextureObject.cpp:96` → `BumpTextureBindGeneration()` - - `MG_State/GLState/TextureState/TextureUnit.cpp:39` → `BumpTextureBindGeneration()` - plus `MG_State/GLState/TextureState/TextureState.cpp:142` → `BumpTextureBindGeneration()`. - All five land in `TextureState::BumpSamplingResolutionGeneration` (`TextureState.h:131`) or - `TextureState::BumpTextureBindGeneration` (`TextureState.h:109`), both hooked. The only other - writes to the three counters in the whole tree are inside `TextureState::NoteUnitTouched` - (`TextureState.h:78-100`), also hooked on both branches. Verified by - `grep -rn "m_textureBindGeneration\|m_samplingResolutionGeneration\|m_maxTouchedUnit"`: no write - outside `TextureState.h`. -* **Three fields re-derived, one field ruled out.** - 1. `GetSamplingResolutionGeneration` — moved by `SamplerObject::BumpVersion` - (`SamplerObject.cpp:34`) and `TextureObjectBase::BumpShapeVersion` (`TextureObject.cpp:37`); - hook at `TextureState.h:131-134`. Covers all sampler-setter and storage-shape writers. - 2. `GetTextureBindGeneration` — moved by `TextureUnit.cpp:39` (sampler unbind), - `TextureObject.cpp:96` (a context default texture becoming defined) and - `TextureState.cpp:142` (delete-unbind), plus the `bindingChanged` branch of `NoteUnitTouched`; - hooks at `TextureState.h:97-99` and `:109-112`. - 3. `GetMaxTouchedTextureUnit` — moved only by the high-water branch of `NoteUnitTouched` - (`TextureState.h:79-86`); hook is inside that branch, after the assignment. - 4. **`GetTouchedBufferBindingPointCount` ruled out** (the one V-class high-water counter the - result file does *not* discuss by name): its only mutator is - `GLContext::TouchBufferBindingPoint` (`Core.h:83`) and its only callers are - `MG_Impl/GLImpl/Buffer/GL_Buffer.cpp:1499,1622` — frontend entry points, never a backend. -* **Everything else checks out.** `GetRenderStateParameters*`/`GetPipelineStateVersion` move only - through `RenderState` setters (`RenderState.cpp`), and `RenderState` is a private member of - `GLContext` with no backend-reachable handle; `DirectGLES`'s `ScopedEmulationDrawState` - (`DirectGLES.cpp:5226-5345`) saves/restores **native** GL through `g_GLESFuncs.glGetIntegerv` - only — no frontend write, so the `IsTransformFeedbackPaused` worry the `kReadback` comment - raises is not a mutation. The transform-feedback counters move only through `GLContext` methods - (`Core.h:308-357`), unreachable from a backend. The `*Impl::` namespaces the backends use - (`BufferImpl`, `TextureImpl`, `SamplerImpl`, `FramebufferImpl`, `RenderStateImpl`) are - **backend-local** (`MG_Backend/DirectGLES/Managers.cpp:494,2751,5078,8490`, - `DirectGLES.cpp:352,1299,1882,1983`), not `MG_Impl`. The one genuine `MG_Impl` helper a backend - calls, `MG_Impl::GLImpl::CopyTextureImageToClientOrPBO_State` (`VulkanRenderer.cpp:10768`, - defined `GL_Texture.cpp:5375`), only reads and `RecordError`s. No `MGP_FILL` is reachable from - inside a verb (no nested fill). - -Conclusion: the enumeration is complete for this tree. I could not find a fourth field. - -### 2d. "A fill row that hides a divergence instead of fixing it" - -A `FillPoints.def` row can only suppress `Fatal{UnmigratedPipeInput}` for that (class, field); the -value it then makes available is still compare-at-read verified against the live context, so a row -cannot hide a `PipeVerifyDiffer`. The two lane-driven rows are correctly derived: -`VkClearManager.cpp:58` reads `MGB_CTX->IsCapabilityEnabled(FramebufferSrgb)` inside -`PreCompensateSrgbClearColor`, reached from `VulkanRenderer::MaterializePendingClearForTexture` -(`:7865`, calls at `:7967,7995`), which `GenerateMipmap` calls at `:11000` (kTextureOp) and -`PrepareStorageImageTextures` (`:5763`) calls from `DispatchCompute` (`:7054`) / -`DispatchComputeIndirect` (`:7106`) (kDispatch). Verified. The other `IsCapabilityEnabled` -consumers (`MaterializeMultisamplePendingClear` `:8038`, `MaterializePendingClearForRenderbuffer` -`:8109`) are reached from `CopyImageSubData` (`:9513`), `ReadPixels` (`:9928`) and -`ReadDepthStencilPixels` (`:10307`) — classes `kBlitOrCopy`/`kReadback`, which already carry the -row. The `kReadback` "reformat" is genuinely row-neutral: the generated `kReadback` mask -(`0x5f50f3a041300541`) is unchanged in the `.inc` diff, and the class own-counts move by exactly -+1/+4/+3 = the 8 added rows. - -### 2e. "A test helper that makes tests pass unconditionally / tests that cannot fail" - -No helper was added or changed; the three tests use the existing `PipeInputsTest` fixture, -`Fresh()`, `RunInChild()`, `ExitedWith()`. Two of the three fail under 2a.3's perturbation. Both -value tests carry their own "the mutation actually took" guards -(`ASSERT_NE(ctx.GetSamplingResolutionGeneration(), samplingBefore)` at `PipeInputsTest.cpp:551`, -and `::_exit(7)` in the child at `:610`), so they cannot pass for the wrong reason. All three exist -as visible `GTEST_SKIP`s in the pull arm (`:173,176,179`) and skip visibly where their switch is -off — confirmed per build: `build-linux` 3 skipped, `build-push` 1 pass + 2 skipped (POISON and -VERIFY are both off there), `build-verify` 3 pass. - -### 2f. "Something moved the pull build" - -`.text`, `.data`, `.bss`, `.rodata`, total size and all 27799 symbols identical, and the `.so` is -the same number of bytes on disk. The `MG_State` header edit is three `MGP_NOTE_MUTATION` -statements plus an include that expands to nothing under `MOBILEGL_PIPE_PUSH=OFF`. - ---- - -## 3. Majors - -**None.** - ---- - -## 4. Minors - -1. **The notice keeps firing between verbs.** `m_currentVerb` is never reset at verb end - (`PipeInputs.h:616` initialises it to `kVerbCount`, and only `MGPipeFillForVerb` writes it), so - after any verb the last verb's mask stays installed and every subsequent frontend - `glBindTexture` / sampler edit made by the *application* runs the full notice - (`PipeFill.cpp:479-491`, including the `CopyField` switch) even though no verb is in flight. - Harmless for correctness — the value only becomes *more* equal to the live context and the stamp - is untouched — but `PipeMutation.h:33` ("a verb has been filled … in that verb class's may-read - mask") reads as if the guard meant "inside a verb", which it does not, and the push build now - pays the notice on the application's bind hot path. A `MGP_FILL`-side reset (or a - `verb-in-flight` flag) would make the comment true and the cost proportional. Worth naming for P2. - -2. **Six of the eight added `FillPoints.def` rows are static over-approximation, not lane-driven.** - Only `X(kDispatch, IsCapabilityEnabled)` (`:179`) and `X(kTextureOp, IsCapabilityEnabled)` - (`:246`) were forced by an observed Fatal. `X(kBlitOrCopy, GetViewportIndexed| - GetDepthRangeIndexed|GetProvokingVertexMode|GetBufferBindingPoint)` (`:228-231`) and - `X(kTextureOp, GetFramebufferBindingSlot|GetBufferBindingPoint)` (`:247-248`) come from a - hand-verified but unexercised path (`TryBlitToDefaultFramebufferWithShader`, - `GenerateDepthMipmapWithShader`). Each such row permanently weakens the poison for that - (class, field) pair. The result file admits this for one row; it applies to five more. - -3. **The notice's mask guard is untested.** `if (!MGPipeFieldMaskHas(mask, field)) return;` - (`PipeFill.cpp:489`) has no covering assertion. - `TheMutationNoticeRefreshesTheValueButNotTheStamp` (`PipeInputsTest.cpp:563-587`) says its - `FenceSync` half proves "the notice stamped a field the verb class never fills", but the notice - never stamps *any* field, so that half re-tests the stamp invariant rather than the mask branch; - removing the mask guard entirely would leave all three tests green. - -4. **`MG_State` now has a link-time dependency on a symbol defined in `MG_Impl`.** - `TextureState.h:11` includes `MG_Pipe/PipeMutation.h`, whose `MGPipeNoteFrontendMutation` is - defined in `MG_Impl/Pipe/PipeFill.cpp`. Fine today (one static library, one `SOURCE_FILES` - list), and the header itself only depends on `MG_Pipe`, but it inverts the `MG_State < MG_Impl` - layering at link time and `PipeMutation.h` is not one of `check_include_closure.py`'s three - probed headers, so nothing gates what that include may drag in later. - -5. **"Every case armed" is not provable per entry for the 818 integration entries.** All - `Verify` entries share one log file per backend - (`MOBILEGL_LOG_FILE_PATH=…/pipe-verify-DirectGLES.log`); after the full lane run that file - contains no `MGPipe` line at all (each process truncates it). Per-entry arming rests on the two - dedicated `PipeVerifyArmingScenario` entries plus the fact that the eight F2 aborts reproduce on - demand (2a.3). Pre-existing lane design, not this package's doing, but the result file's "(d)" - should not be read as per-entry arming evidence. The 79-case retrace *is* per-case armed (79/79). - -6. **`ctest -L integration-verify` also reports 77 `Skipped` entries** among the 818 (741 real - passes). Not caused by this package (no `MG_IntegrationTest` file is touched), but the "818 - passed" figure in the result file is 741 executed + 77 skipped. - -7. **Two small undeclared deviations.** (a) `build-linux/CMakeCache.txt` in this tree carries - `CMAKE_CXX_COMPILER:UNINITIALIZED=clang++` while the reference `~/w7/pipe/build-linux` and this - tree's own `build-push`/`build-verify` carry `CMAKE_CXX_COMPILER:STRING=/usr/sbin/clang++`; the - `.text` byte-identity shows it resolves to the same compiler, but the G1 comparison was made - across a differently-spelled cache entry and deviation 2 does not mention it. - (b) `MobileGL/MG_Pipe/PipeMutation.h` is a new file in a directory the P1 brief's D5 table - enumerates; the new row is not called out as a deviation (it is implied by the findings' - preferred option, so this is bookkeeping only). - ---- - -## 5. Follow-ups for the integrator (not blocking) - -- The F2 enumeration in `fix-core-v1.md` still has to land in `docs/Disaggregated/MEASUREMENTS.md` - (the package correctly left it alone; the findings asked for the enumeration to exist, and it does). -- Minor 1 (verb-end reset) and minor 3 (mask-guard coverage) are natural P2 items when the tracker - takes over the filler. -- `GetFramebufferBindingSlotFast`'s five bypassing reads (`DirectGLES.cpp:1611,1905,2743,2858,2933`) - remain outside both the poison and the compare-at-read; unchanged, and correctly recorded. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-v1.md deleted file mode 100644 index 01d752b1..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-core-v1.md +++ /dev/null @@ -1,284 +0,0 @@ -# P1 lane fixes — package `fix-core` (F1 + F2) - -Tree: WSL `~/w7/p1-fix-core`, branch `p1/fix-core`, branched from the integrated -`feat/disaggregated` @ `97b997d5`. Not pushed. - -## Commits - -| sha | subject | -|---|---| -| `80a6b390` | `[Fix] (Pipe): fill the capability set on a texture op and a dispatch, and the shader blit's viewport, provoking vertex and buffer bindings` | -| `6b681c4a` | `[Fix] (Pipe, State): refresh a pushed PipeInputs field when the frontend moves it inside a verb` | -| `9bd6d394` | `[Test] (Pipe): pin the push-on-mutation shape - a frontend write inside a verb refreshes the pushed field, and only its value` | - -Files touched: `MobileGL/MG_Pipe/FillPoints.def`, -`MobileGL/MG_Pipe/generated/PipeFillPoints.inc` (regenerated), -`MobileGL/MG_Pipe/PipeMutation.h` (new), `MobileGL/MG_Impl/Pipe/PipeFill.cpp`, -`MobileGL/MG_State/GLState/TextureState/TextureState.h`, -`MobileGL/MG_Test/Pipe/PipeInputsTest.cpp`. No backend file, no other package's file. - ---- - -## F1 — missing fill rows - -### What the lane proved - -`~/w7/retrace-out/p1-verify/` aborted four cases with -`Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@GenerateMipmap"}` (3) and -`"IsCapabilityEnabled@DispatchCompute"` (1), all `DirectVulkan`. - -Root cause, traced in the tree: `VulkanRenderer::MaterializePendingClearForTexture` -(`VulkanRenderer.cpp:7865`) calls `VkClearManager::PreCompensateSrgbClearColor`, which reads -`IsCapabilityEnabled(FramebufferSrgb)` (`VkClearManager.cpp:58`). It is reached from -`GenerateMipmap` (`:11000`, a `kTextureOp` verb) and from `DispatchCompute` through -`PrepareStorageImageTextures` (`:5763`, a `kDispatch` verb). `kDraw`, `kClear`, `kBlitOrCopy` -and `kReadback` already named the field; those two classes did not. - -### The audit that followed - -Not stopped at the two rows. A static verb-class → field reachability pass over `MG_Backend` -(name-based call graph, roots taken from both `GetBackendFunctions()` tables; -`scratchpad/wf4/fix-core/reach.py`) produced candidate rows with their call paths, and each -candidate was then hand-verified against the tree — the pass merges same-name functions, so -most candidates were collisions (`Clear`, `SyncToBackend`, `GetOrCreatePipeline` on -`PipelineFactory` vs `VulkanRenderer`) and were dropped. Two real families survived: - -| class | rows added | verified path | -|---|---|---| -| `kDispatch` | `IsCapabilityEnabled` | `DispatchCompute` → `PrepareStorageImageTextures` → `MaterializePendingClearForTexture` → `PreCompensateSrgbClearColor` | -| `kTextureOp` | `IsCapabilityEnabled` | `GenerateMipmap` (`VulkanRenderer.cpp:11000`) → the same | -| `kTextureOp` | `GetFramebufferBindingSlot`, `GetBufferBindingPoint` | `GenerateMipmap` → `GenerateDepthMipmapWithShader` (`:11069`) → `BindProgramUniformBuffers` (`:4755`) → `ResolveSamplerDescriptor` (`UniformManager.cpp:555`, the feedback-loop check) / `ResolveStorageBufferDescriptor` (`:1194`) | -| `kBlitOrCopy` | `GetViewportIndexed`, `GetDepthRangeIndexed` | `BlitNamedFramebuffer` → `TryBlitToDefaultFramebufferWithShader` (`:8671`) → `ApplyGLViewportState` (`:8502`) → `ComputeGLViewport` | -| `kBlitOrCopy` | `GetProvokingVertexMode` | same → `GetOrCreateBlitPipeline` (`:8511`) → `SelectProvokingVertexMode` (`:4434`) | -| `kBlitOrCopy` | `GetBufferBindingPoint` | same → `BindProgramUniformBuffers` (`:8576`) → the buffer-block resolvers | - -Every added row carries its derivation in a comment in `FillPoints.def`. The commit also -unfolds the `kReadback` transform-feedback rows that `9087f133` landed on one 1200-column -line; no row changed there. - ---- - -## F2 — the backend mutates frontend state inside a verb - -### Option chosen: **push on mutation** (the findings' preferred option) - -Justification: - -- It keeps the comparator's invariant — "the pushed block equals the live context at every - read" — literally true, so the push build's semantics stay equal to the pull build's. The - fallback (a volatile-in-verb class) would instead have made the push build read the - boundary value where the pull build reads the moved one, i.e. it would have *kept* the - behavioural difference and hidden it. -- The compare-at-read arm is the only comparator arm that is real in P1 (the entry compare is - tautological until P2's tracker fills the first arm, per brief D8). The fallback's mechanism - is precisely "skip compare-at-read for this field", which would blind the P1 gate on the one - field that found the bug. -- It is the shape P2's tracker needs anyway (findings, F2 bullet 1). - -### Mechanism - -`MobileGL/MG_Pipe/PipeMutation.h` (new, leaf layer that both `MG_State` and `MG_Impl` may -include) declares `MGP_NOTE_MUTATION(Field)`. In the pull build the macro is `((void)0)` and -the header includes nothing. Under `MOBILEGL_PIPE_PUSH` it calls -`MGPipeNoteFrontendMutation(field)`, defined in `MG_Impl/Pipe/PipeFill.cpp` next to the filler -whose `MGPipeFillAccess::CopyField` it reuses. The notice refreshes one field's **value** when - -1. a context is live, -2. a verb has been filled (`m_currentVerb != kVerbCount`), -3. the field is not sticky (a forwarded field has no storage), and -4. the field is in the current verb class's `kMGPipeClassFieldMask`. - -It never touches the poison stamp, so a stamp `MOBILEGL_PIPE_POISON_OMIT` withheld stays -withheld (negative control B survives) and a field the verb never filled stays -`Fatal{UnmigratedPipeInput}` on the next read instead of being healed by an unrelated write. - -### The enumeration asked for - -**Step 1 — what a backend can write into the frontend.** `scout-pull-sites.md` §2.6 lists the -sites; re-derived live on this tree by matching every non-const `MG_State` method declaration -against every call in `MG_Backend/**` (`scratchpad/wf4/fix-core/scan.py`). Nothing is reached -through `MGB_CTX` itself: the only non-read accessors the backends call are `RecordError`, -`InvalidateCompileEnv` and `ValidateProgramName`, all three F-class/sticky with no storage. -Everything else is a method call on an object the backend obtained by handle or created itself. - -| written frontend object | mutators the backends call | pushed field it can move | -|---|---|---| -| `SamplerObject` | `SetMinFilter`, `SetMagFilter`, `SetMipmapMode`, `SetWrapS/T/R`, `SetLodRange`, `SetCompareMode`, `SetSamplerCompareFunc` (`VulkanRenderer.cpp:4270-4276`) — each ends in `SamplerObject::BumpVersion()` (`SamplerObject.cpp:34`) | **`GetSamplingResolutionGeneration`** | -| `ITextureObject` | `AllocateStorage`, `MarkStorageDirty`, `TruncateMipmapLevels`, `SetInternalFormat`, `SetSamples`, `SetFixedSampleLocations` (`UniformManager.cpp:1484-1498,1625-1635` fallback-texture creation; `DirectGLES.cpp:6326-6327`) — each ends in `TextureObjectBase::BumpShapeVersion()` (`TextureObject.cpp:37`); `SetInternalFormat` on a context default texture additionally bumps the bind generation (`TextureObject.cpp:96`) | **`GetSamplingResolutionGeneration`**, **`GetTextureBindGeneration`** | -| texture unit / texture state | any bind path (`TextureState::NoteUnitTouched`), `TextureUnit::SetSamplerObject`, `TextureState::MarkTextureObjectForDeletion` | **`GetTextureBindGeneration`**, **`GetMaxTouchedTextureUnit`** | -| `BufferObject` | `MarkGpuWritten`, `SetBackendResource`, `EnsureGpuResidentStorage`, `WritebackFromBackend`, `SyncPersistentMappedRange`, `SyncGpuWrites` | none — buffers are reached through the O-class binding slots/points, which are live pointers into the context, so a content or resource change is visible to the backend without any copy | -| `ProgramObject` | `SetDrawID`, `SetBaseVertex`, `SetBaseInstance`, `SetViewportPassMask`, `AttachShader`, `Link`, `MarkUBOContentDirty`, `SetBackendHashMemo` | none — `GetProgramForDraw`/`ForDispatch`/`TransformFeedbackProgram` are O-class `SharedPtr` copies; the pointee is shared, so its mutations are seen live and the handle itself never moves inside a verb | -| `VertexArrayObject` | `SetBackendHashMemo`, `SetBackendStateMemo`, `SetBackendAuxMemo` | none — same reason (`GetBoundVertexArray` is O-class) | -| `GLContext` | `RecordError`, `InvalidateCompileEnv` | none — both are sticky F-class with no storage | - -**Step 2 — the frontend mutators that bump a pushed value.** Only six `PipeInputs` fields are -*derived* counters rather than plain state copies: `GetSamplingResolutionGeneration`, -`GetTextureBindGeneration`, `GetMaxTouchedTextureUnit`, `GetTextureContextId` (immutable per -context), `GetRenderStateParametersVersion` / `GetPipelineStateVersion` (moved only by -`RenderState`'s setters, which no backend calls — the scan finds no `RenderState` mutator in -`MG_Backend`, and `DirectGLES`'s `ScopedEmulationDrawState` / `ScopedRestartIndexSubstitution` -save and restore *native* GL state, never frontend state), plus the transform-feedback -counters (`AddTransformFeedback*`, `EndTransformFeedback` — `GLContext` mutators no backend -calls). - -**Step 3 — the intersection, i.e. what is handled.** Three fields: - -| field | hook site | -|---|---| -| `GetSamplingResolutionGeneration` | `TextureState::BumpSamplingResolutionGeneration()` | -| `GetTextureBindGeneration` | `TextureState::BumpTextureBindGeneration()` and the `bindingChanged` branch of `TextureState::NoteUnitTouched()` | -| `GetMaxTouchedTextureUnit` | the high-water branch of `TextureState::NoteUnitTouched()` | - -The hooks sit on the **counters**, not on the individual writers, so every path in the table -above (and any writer added later) is covered by three notices rather than forty. - ---- - -## Verification (all run in `~/w7/p1-fix-core`) - -Build directories configured to the brief's C recipe: `build-linux` (pull), -`build-push` (`MOBILEGL_PIPE_PUSH=ON`), `build-verify` (`MOBILEGL_PIPE_VERIFY=ON`, -`MOBILEGL_ITEST_REQUIRE_GPU=ON`); `CCACHE_BASEDIR=/home/swung/w7`; all three build clean. - -**(a) pull build symbol-identical** - -``` -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/.bss/.rodata/Total all +0 - 27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed - Removed (0) / Added (0) / Resized (0) / Renamed only (0) -``` - -**(b) generator** - -``` -python3 scripts/gen_pipe.py --check -> "generated files are up to date" - 71 calls, 69 verify payloads, 63 fields (7 sticky), - 69 verbs, 9 classes, 0 UNMAPPED -python3 scripts/gen_pipe.py --self-test -> "6 negative-control trip(s), positive control OK" -``` - -**(c) `ctest -L unit`** - -| build | result | -|---|---| -| `build-linux` | **100% tests passed, 0 failed out of 1485** | -| `build-push` | 11 failed out of 1485 | -| `build-verify` | 11 failed out of 1485 | - -The 11 in push/verify are **exactly** the F3 list owned by the other package -(`DirectGLESSanity.{BindsAMultisampleTextureDespiteTheDefaultMipmapFilter, -BindingZeroClearsPreviousNativeTextureBinding}`, `DirectGLESTextureSync.UnitMemoRefusesToDrive -ATwinFromAnotherTexture`, the seven `FramebufferTest.*`, `TextureTest.StorePackedWordsToClient -CopiesWordsVerbatimUnderPackParams`) — they construct a `GLContext` by hand and call a backend -helper with no verb in flight, and they are not mine to fix. - -**(d) `ctest -L integration-verify` in `build-verify`** - -``` -ctest --test-dir build-verify -L integration-verify --no-tests=error -j 4 - exit=0 - 100% tests passed, 0 tests failed out of 818 - grep -c "Fatal{UnmigratedPipeInput" -> 0 - grep -c "Fatal{PipeVerifyDiffer" -> 0 -``` - -The eight `DirectVulkan.Verify.{UnboundImageDescriptorScenario,SampledSetStalenessScenario}.*` -aborts of F2 are gone. The four poison/verify control entries -(`DirectGLES|DirectVulkan.{VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported, -PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb}`) still pass, i.e. the gate -can still go red for its reason. - -**(e) full 79-case retrace under verify** - -``` -cp .../wf2/retrace_gate.py ~/w7/retrace_gate.py # byte-identical to the one already there -MOBILEGL_PIPE_VERIFY=1 python3 ~/w7/retrace_gate.py \ - --tree ~/w7/p1-fix-core --lib ~/w7/p1-fix-core/build-verify/libMobileGL.so \ - --out ~/w7/retrace-out/fix-core -j 4 - 79 tests selected of 79 - passed 79 / 79; failed: [] -``` - -``` -cd ~/w7/retrace-out/fix-core -ls *.log -> 79 -grep -l "armed, zero divergences, zero unmigrated reads" -> 79 -grep -l "Fatal{" *.log -> 0 -grep -r "Fatal{|UnmigratedPipeInput|PipeVerifyDiffer" . -> 0 each -``` -Sample summary line: `-- MGPipe verify: improved-transparency-minecraft-26.3 DirectGLES armed, -zero divergences, zero unmigrated reads`. - -**(f) `ctest -N` name diff** - -``` -ctest --test-dir build-linux -N | grep -E '^\s+Test +#[0-9]+: ' \ - | sed -E 's/^ *Test +#[0-9]+: //' | sort > ~/w7/fc-ctest-names.txt -comm -23 ~/w7/p1-before-ctest-names.txt ~/w7/fc-ctest-names.txt | wc -l -> 0 (removed) -comm -13 ~/w7/p1-before-ctest-names.txt ~/w7/fc-ctest-names.txt | wc -l -> 29 (added) -``` -26 of the 29 came in with the merged P1 packages; the 3 new ones are this package's. - -**Extra: include-closure gate** - -``` -python3 scripts/check_include_closure.py -> 3 probes, 0 skipped, 0 problem(s) -``` - -**Extra: falsification of the new tests.** With `MGPipeNoteFrontendMutation`'s body -short-circuited and `build-verify` rebuilt, -`PipeInputsTest.AFrontendMutationInsideAVerbRefreshesThePushedField` and -`...DoesNotDivergeAtRead` both FAIL (the second by `SIGABRT` on -`Fatal{PipeVerifyDiffer, "GetSamplingResolutionGeneration@DrawArrays", where=read}` — the lane -failure reproduced in a unit test); `...TheMutationNoticeRefreshesTheValueButNotTheStamp` stays -green, which is correct for a guard case. The edit was reverted and the build restored before -committing (`git diff` clean on that file). - ---- - -## Deviations - -1. **Tree path.** The brief names the tree `~/w7/fix-core`; the worktree that exists is - `~/w7/p1-fix-core` on branch `p1/fix-core` (`git worktree list` at `97b997d5`). Tree wins - for facts; everything was done there. -2. **Build directories did not exist.** The brief says the tree "has three configured build - directories". `build-linux`, `build-push` and `build-verify` were absent; they were - configured from the brief's C recipe (matching `~/w7/p1-core`'s `CMakeCache.txt` flags: - Release, `/usr/sbin/clang++`, ccache, INFO, `MOBILEGL_BUILD_TEST=ON`, - `MOBILEGL_BUILD_INTEGRATION_TEST=ON`, `MOBILEGL_BUILD_BENCHMARK=OFF`, - `MOBILEGL_BUILD_TRACE_REPLAY=OFF`, mesa EGL vendor, lvp ICD; - `MOBILEGL_ITEST_REQUIRE_GPU=ON` only for verify). -3. **A fourth file under `MG_State`.** The brief allows `MG_State/**` "only if F2's chosen - mechanism needs a frontend hook" — it does; `TextureState.h` is the only `MG_State` file - touched, and only by three macro statements plus one include that expands to nothing in the - pull build (`.text` delta 0 confirms it). -4. **`ctest -L integration-verify` counts 818 entries**, not the brief's 742. The label's - membership grew with the merged P1 packages; nothing was excluded. -5. **Reformatting inside `FillPoints.def`.** The `kReadback` transform-feedback rows that - `9087f133` landed on a single 1200-column line were unfolded to the file's one-row-per-line - shape. No row was added, removed or moved between classes by that edit. -6. **The static reachability pass is a helper, not a gate.** `scratchpad/wf4/fix-core/reach.py` - over-approximates through same-name functions; it was used to generate candidates, each of - which was then confirmed or rejected by reading the tree. It is not wired into CI. - -## Unfinished / known gaps - -- **F3 is not fixed here** (it is the other package's): 11 `ctest -L unit` entries still fail - in `build-push` and `build-verify` on this branch, and they will keep failing until - `p1/fix-tests` lands. `build-linux` is 1485/1485. -- **`GetFramebufferBindingSlotFast`** (`DirectGLES.cpp:1611,1905,2743,2858,2933`) still - bypasses the accessor once its cache is warm, so those five reads are neither poison-checked - nor compare-at-read verified. That is the known P2 gap recorded in brief D4, untouched here. -- **The notice is single-threaded**, exactly like the filler it shares state with: both write - `gPipeInputs` with no synchronisation. Unchanged risk profile, but worth naming before P2 - puts a tracker behind the same global. -- **`GetBufferBindingPoint` on `kTextureOp`/`kBlitOrCopy` is a "may-read" row**, not one the - lane exercised: the helper programs those two verbs draw with declare no SSBO today, so the - resolver that reads the binding points is reachable in code but not hit by the 79 traces or - the 818 integration entries. It was added because the path exists; if the table is ever - trimmed for fill cost, that is the first row to re-examine. -- Nothing was pushed, and `docs/Disaggregated/MEASUREMENTS.md` was not edited (not in this - package's file list); the F2 enumeration above is the material an integrator would land there. diff --git a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-tests-review-v1.md b/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-tests-review-v1.md deleted file mode 100644 index d1964cde..00000000 --- a/docs/Disaggregated/notes/recovered/wf4/p1-results/fix-tests-review-v1.md +++ /dev/null @@ -1,272 +0,0 @@ -# Adversarial review — P1 package `fix-tests` (F3), branch `p1/fix-tests` @ `c67c47f7` - -Reviewer ran independently in WSL `~/w7/p1-fix-tests` (the tree; the task text's `~/w7/fix-tests` -does not exist — the implementer declared this, deviation 1, and it is correct). -Tree state at start and at end: `git status --porcelain` shows only the three untracked build -directories; `git log -1` is `c67c47f7`. Nothing was left changed (two temporary mutations were -made for negative controls and reverted with `git checkout --`; verified clean afterwards). - -**Verdict: approved. 0 majors, 6 minors.** - -I set out to refute "correct and complete" and could not. Every claim in -`fix-tests-v1.md` reproduced, and two of them reproduce *stronger* than claimed. - ---- - -## 1. What was re-run independently (command + observed output) - -### 1.1 The diff is what it says it is - -``` -$ git -C ~/w7/p1-fix-tests diff --name-only 97b997d5..HEAD -MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp -MobileGL/MG_Test/SanityTest.cpp -MobileGL/MG_Test/ScopedPipeVerb.h -MobileGL/MG_Test/Texture/TextureTest.cpp -``` - -128 insertions / 5 deletions, all inside `MobileGL/MG_Test/**`. No production source, no -`scripts/`, no CMake, no `.def`/generated file, no fixture, no `MG_Test/Pipe/**` (the other -package's area), no `MG_IntegrationTest/**`. The full diff was read line by line. - -Commit message shape checked against the lane rule (subject + blank line + `- ` bullets, no -`Co-Authored-By`): conforms. - -### 1.2 Build configurations are the reference ones (deviation 2/3 verified, not taken on trust) - -Diffing every `MOBILEGL_*` / `CMAKE_BUILD_TYPE` / `CMAKE_CXX_COMPILER` cache entry against -`~/w7/pipe/

/CMakeCache.txt`: - -``` ---- build-linux --- IDENTICAL to pipe/build-linux ---- build-push --- only CMAKE_CXX_COMPILER:STRING vs :UNINITIALIZED (same value) ---- build-verify --- only CMAKE_CXX_COMPILER:STRING vs :UNINITIALIZED (same value) -``` - -`MOBILEGL_BUILD_BENCHMARK:BOOL=OFF` is present in `~/w7/pipe`'s three caches too, so -deviation 3 reproduces the reference trees rather than changing them. Compile definitions -confirmed from `build.ninja`: `build-linux` has neither, `build-push` has -`-DMOBILEGL_PIPE_PUSH=1`, `build-verify` has `-DMOBILEGL_PIPE_PUSH=1 -DMOBILEGL_PIPE_VERIFY=1`. - -`cmake --build {build-linux,build-push,build-verify} -j 12` → all three `ninja: no work to do`, -i.e. the committed tree is exactly what was tested. - -### 1.3 Pull-build symbol report, `--threshold 0` - -``` -$ python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so \ - --after build-linux/libMobileGL.so --threshold 0 -symbol-report: .text 10792579 -> 10792579 (+0, +0.000%) -symbol-report: .data 76824 -> 76824 (+0) .bss 1296872 -> 1296872 (+0) .rodata 1534938 -> 1534938 (+0) -symbol-report: 27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed -``` - -G1 holds. (Trivially, by construction — the helper is header-only test code — but re-run.) - -### 1.4 `gen_pipe.py --check` / `--self-test` - -``` -gen_pipe: 71 calls (11 screen, 60 context), 69 verify payloads, 63 PipeInputs fields (7 sticky), 69 verbs, 9 classes -gen_pipe: generated files are up to date rc=0 -gen_pipe: self-test: 6 negative-control trip(s), positive control OK rc=0 -``` - -`scripts/check_include_closure.py` also clean (3 probes, 0 forbidden) — the new header does not -break the layering gate. - -### 1.5 `ctest -L unit` in all three build directories - -``` -build-linux rc=0 100% tests passed, 0 tests failed out of 1482 -build-push rc=0 100% tests passed, 0 tests failed out of 1482 -build-verify rc=0 100% tests passed, 0 tests failed out of 1482 -``` - -### 1.6 `ctest -N` name diff - -``` -build-linux 2360 names, build-push 2360, build-verify 3178 -removed vs ~/w7/p1-before-ctest-names.txt: 0 added: 26 -vs the integrated tree ~/w7/pipe/build-linux (2360): 0 removed, 0 added <-- exact match -``` - -The 26 additions are the other P1 packages' (already at the branch point); **this package adds -none, removes none, renames none.** The `build-verify` surplus is confirmed by label census: -`integration-verify` 818 + `integration-gpu` 1696; `~/w7/pipe/build-verify` also lists 3178. -So the surplus is the configuration, not a name change. - ---- - -## 2. The four things I tried to catch it on - -### 2.1 "A test helper that makes tests pass unconditionally" — refuted, twice - -**Control A (mine, stronger than the implementer's): neutralise the helper entirely.** -`sed -i 's/^#if MOBILEGL_PIPE_PUSH$/#if defined(MGL_REVIEW_NEUTRALISE) \&\& MOBILEGL_PIPE_PUSH/'` -on `MobileGL/MG_Test/ScopedPipeVerb.h` (all four guards, so ctor, `Renew()` and dtor become -no-ops in every build), rebuild `build-verify`, `ctest -L unit -j 8`: - -``` -99% tests passed, 11 tests failed out of 1482 - 5 DirectGLESSanity.BindsAMultisampleTextureDespiteTheDefaultMipmapFilter (Subprocess aborted) - 6 DirectGLESSanity.BindingZeroClearsPreviousNativeTextureBinding - 82 DirectGLESTextureSync.UnitMemoRefusesToDriveATwinFromAnotherTexture -310 FramebufferTest.DrawIntoAWidenedDrawBufferReachesTheDriverWithAlphaWritesMaskedOff -311 FramebufferTest.ClearIntoAWidenedDrawBufferKeepsAlphaWritableAndSubstitutesOne -312 FramebufferTest.ApplicationAlphaMaskOffIsStillHonouredOnANativeDrawBuffer -313 FramebufferTest.DualSourceBlendFactorsReachTheDriverWhenTheExtensionIsThere -314 FramebufferTest.DualSourceBlendIsDeclinedRatherThanThrownWhenTheExtensionIsMissing -315 FramebufferTest.DualSourceFactorsAreDeclinedEvenWithBlendingDisabled -316 FramebufferTest.DualSourceFactorsWithBlendingDisabledStillReachACapableDriver -471 TextureTest.StorePackedWordsToClientCopiesWordsVerbatimUnderPackParams -``` - -Exactly eleven, and exactly F3's eleven — every declaration in the diff is load-bearing (none is -decorative), and no *other* test in the 1482 depends on the helper. So the helper cannot be -making anything pass that was not already failing for the F3 reason, and none of these eleven is -a test that cannot fail. - -**Control B (mine): substitute the WIDEST verb class for a narrow one.** In -`MobileGL/MG_Test/Texture/TextureTest.cpp:4481` change `MGPipeVerb::ReadPixels` → -`MGPipeVerb::DrawArrays` (kDraw, 47 fields, the largest mask there is) and run that one test in -`build-verify` with `MOBILEGL_LOG_FILE_PATH` set: - -``` -exit=134 (SIGABRT) -[Linux TextureTest/FATAL]: MGPipe: Fatal{UnmigratedPipeInput, "GetPixelStoreParameters@DrawArrays"} -``` - -This is the decisive anti-rubber-stamp result: the ctor runs the *production* -`MGPipeFillForVerb` (`MobileGL/MG_Impl/Pipe/PipeFill.cpp:528-575`), which stamps only -`kMGPipeClassFieldMask[class of verb]` — so a read outside the declared verb's may-read set is -still `Fatal{UnmigratedPipeInput}`, naming the field *and* the declared verb, even when the -declared verb is the broadest one. `FillPoints.def` remains the only thing that says what a verb -may read. It also proves the test binary and the backend share one `gPipeInputs`: the abort came -out of the backend's accessor, on the block the test's ctor stamped. - -Both were reverted with `git checkout --` and the affected targets rebuilt; the restored tests -pass and `git status --porcelain -- MobileGL` is empty. - -### 2.2 "Fields / call sites the enumeration missed" — refuted - -Enumerated every direct backend-helper call in the unit tree myself -(`awk` over the three files, plus a repo-wide grep): - -* `MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp` — 11 `SyncRenderState(` calls at - `:1238,1271,1283,1323,1356,1377,1395,1422,1440,1453,1471`; **every one** sits inside a - `ScopedPipeVerb` scope or after a `Renew()`. -* `MobileGL/MG_Test/SanityTest.cpp` — 4 `BindCurrentTextures()` (`:329,372,380,391`) and - 2 `SyncNeccessaryTextures()` (`:3025,3046`); all covered. -* `MobileGL/MG_Test/Texture/TextureTest.cpp` — 2 `StorePackedWordsToClient(` (`:4482,4508`); - both covered. -* `grep -rlE 'MG_Backend::(DirectGLES|DirectVulkan)::[A-Za-z_]+::[A-Za-z_]+\(' - MobileGL/MG_Test --include=*.cpp` returns exactly those three files and no others. -* The same grep over `MobileGL/MG_IntegrationTest` returns **nothing** — integration scenarios - drive GL entry points only, so `MGP_FILL` always fires there. The implementer's stated reason - for not touching `MG_IntegrationTest/**` is factually right, not an excuse. - -### 2.3 "A mechanism that silences the comparator" — refuted, and the package under-claims - -The unit lane in `build-verify` runs with `Features.PipeVerify` **off** unless -`MOBILEGL_PIPE_VERIFY=1` is in the environment, so the implementer's green `ctest -L unit` only -exercised the poison, not the compare-at-read hook. That was the one place a wrongly *placed* -declaration (fill before a mutation the helper must see) could hide: the push value would go -stale against the live context and nobody would look. So I armed it: - -``` -$ MOBILEGL_PIPE_VERIFY=1 ctest --test-dir build-verify -L unit --no-tests=error -j 8 -rc=0 100% tests passed, 0 tests failed out of 1482 -``` - -and proved the arming was real rather than a silent no-op: - -``` -$ MOBILEGL_PIPE_VERIFY=1 MOBILEGL_LOG_FILE_PATH=/tmp/mgl-verify.log \ - ./build-verify/.../FramebufferTest --gtest_filter='*DrawIntoAWidened*' -[Linux FramebufferTest/INFO]: MGPipe: verify armed - 63 fields, 69 verbs, fatal=1 -# same run without the env var: 0 occurrences of "MGPipe: verify armed" -``` - -With the comparator genuinely armed, every accessor read in all eleven tests returned exactly -what a live pull would have returned. That is a stronger statement than the result file makes, -and it closes the placement question completely: no `ScopedPipeVerb` in this diff is placed -before a mutation its helper is meant to see. - -### 2.4 "The pull build moves" / "a global escape hatch" / "the poison is weakened" — refuted - -* Symbol report 0/0/0/0, `.text` +0 (§1.3). The class body is `#if MOBILEGL_PIPE_PUSH`-gated to - nothing (`MobileGL/MG_Test/ScopedPipeVerb.h:46,62,67`), the ctor signature is identical in - both arms, so call sites cannot rot. -* No global escape hatch: no `#define`, no env knob, no weakening of `MGP_INPUT_CHECK`, no - change to `PipeInputs.h`/`PipeFill.cpp`/`FillPoints.def`. The whole mechanism is "call the - production filler for a named verb". -* Whole-binary single-process runs reproduce (the destructor re-arm's stated purpose): - `SanityTest` exit=0 82 tests, `FramebufferTest` exit=0 61 tests, `TextureTest` exit=0 - 192 tests, 0 `[ FAILED ]` lines each. With the helper neutralised the same three binaries - are 134/1/1 with 11 and 3 failures — i.e. the re-arm is not covering anything up. - ---- - -## 3. Minors - -1. **Nested scopes silently invalidate the enclosing declaration, and the header does not say - so.** `MobileGL/MG_Test/ScopedPipeVerb.h:68` — the destructor bumps `CurrentVerbSerial`, so - closing an inner scope makes the *outer* scope's stamps stale. The two current sites survive - only because a `draw.Renew()` happens to follow - (`FramebufferTest.cpp:1453` after the `Clear` scope at `:1439-1441`). The header's guidance - at `:38-42` documents `Renew()` as "a second verb of the same kind begins" and nested scopes - as the way to handle another class, but never states the interaction. A future author who - nests a scope and then calls the outer helper again gets an abort. One sentence in the - `Renew()` comment would fix it. - -2. **The exit fill makes the next poison Fatal name a misleading verb.** - `ScopedPipeVerb.h:74` picks `MGPipeVerb::GetGpuTimestampNs` as the leave-verb, so a test that - forgets a declaration *after* another scope closed aborts with - `Fatal{UnmigratedPipeInput, "@GetGpuTimestampNs"}` — reading as "a timestamp query - tried to read this", which is not what happened. The implementer's own demo A produced the - clearer `@` only because that test declared nothing at all. A dedicated - "left the verb" sentinel (or documenting the string) would keep the diagnostic honest. - -3. **Only one of the verb classes that reach each helper is exercised.** - `SanityTest.cpp:3025,3046` declare `DrawArrays` for `SyncNeccessaryTextures()`, but that - helper is reached in production from `kReadback` too — `DirectGLES.cpp:9020` (`ReadPixels`) - and `:9191` (`GetTexImage`), among 14 call sites. Choosing the class the helper "belongs to" - is exactly what F3 asks for, and the retrace / integration lanes are the oracle for the other - classes (that is F1's job), so this is not a defect — but the result file's table reads as if - the declared verb were the only class involved, and it is not. - -4. **The known `GetFramebufferBindingSlotFast` poison gap is what produced the pre-fix push - SEGFAULT, and it is worth recording.** `DirectGLES.cpp:147-157`: when the block is unfilled, - `MGB_CTX_IDENTITY` is `nullptr` and `g_fbSlotCacheContext` is also `nullptr`, so the cache is - treated as warm and `*g_fbSlotCache[target]` dereferences null. This is the documented D4 - gap (brief line 151, closed in P2), not this package's, but it means a push-build test that - reaches that path can crash or read a stale slot without the poison ever firing — the one - place where "green" is not proof. No such test exists today (§2.2). - -5. **A brief-vs-tree fact, recorded per the standing instruction.** The brief (G3) calls - `integration-verify` "the 742-entry lane" and `P1-LANE-FINDINGS.md` says 878 integration - entries; the tree says **818** `integration-verify` and 1696 `integration-gpu` - (`ctest --test-dir build-verify -N -L