diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 325875ab..726a05ae 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -1490,9 +1490,13 @@ namespace MobileGL::MG_Backend::DirectGLES { hostBaseFrom, hostBaseTo, start, end); } #endif + // Read per range, like the pull arm: a ring copy staged for an earlier range of + // this same drain puts every later partial range of the store behind it + // (InvalidateFlushAccessFor's queued-copy rule, dev f973008c). + const Bool ringCopyInFlight = resource.ringCopyRetireSerial > DirectGLES::CompletedFrameSerial(); const GLbitfield access = mapUsable ? InvalidateFlushAccessFor(start, end, coveredFrom, coveredTo, limit, - resource.storageSize) + resource.storageSize, ringCopyInFlight) : 0u; if (access != 0) { BindBufferId(TempBufferTarget, resource.id); @@ -1512,6 +1516,8 @@ namespace MobileGL::MG_Backend::DirectGLES { BindBufferId(GL_COPY_WRITE_BUFFER, resource.id); g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, (GLintptr)ringOffset, (GLintptr)start, (GLsizeiptr)size); + // Commands recorded now retire with the fence the next Present inserts. + resource.ringCopyRetireSerial = DirectGLES::CurrentFrameSerial() + 1; } else { UploadRangeFrom(resource, hostBase, start, end); } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 90d55482..463829f5 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -1005,10 +1005,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // matches, exactly like the per-VAO memo stamps. Uint64 drawCleanEpoch = 0; // Frame serial whose completion retires the last glCopyBufferSubData the upload - // ring staged INTO this store (FlushPendingRangesNow's ring tier); 0 = none. Until - // it retires, FlushPendingRangesNow keeps every partial range off the - // range-invalidating map: on Adreno 830 that map, on a store with a queued copy, - // loses the copy (see FlushPendingRangesNow). Draw thread only, like the flush. + // ring staged INTO this store (the flush ladder's ring tier: FlushPendingRangesNow + // on the pull arm, FlushPendingRangesFrom on the push arm); 0 = none. Until it + // retires, both keep every partial range off the range-invalidating map: on + // Adreno 830 that map, on a store with a queued copy, loses the copy (see + // FlushPendingRangesNow and InvalidateFlushAccessFor). Draw thread only, like the + // flush. Uint64 ringCopyRetireSerial = 0; // Zero-copy coherent persistent map (EXT_buffer_storage): the GL store is // immutable, persistently+coherently mapped, and persistentPtr is what the app @@ -1192,10 +1194,14 @@ namespace MobileGL::MG_Backend::DirectGLES { // ("do not take tier 1"), which drops the range onto the staging ring and costs a copy // rather than a corruption. // + // `ringCopyInFlight`: the store still has an upload-ring glCopyBufferSubData queued that the + // GPU may not have run (GLESBufferResource::ringCopyRetireSerial not yet retired). + // // Returns the glMapBufferRange access bits, or 0 when tier 1 must not be taken. inline constexpr SizeT kEsprytInvalidateRangeMinBytes = 128u * 1024u; constexpr GLbitfield InvalidateFlushAccessFor(SizeT queuedStart, SizeT queuedEnd, SizeT mapStart, - SizeT mapEnd, SizeT limit, SizeT storageSize) { + SizeT mapEnd, SizeT limit, SizeT storageSize, + Bool ringCopyInFlight) { if (mapEnd <= mapStart) return 0u; // THE WIDENING REFUSAL. Not >=, not "covers": exactly, in both directions. A map // narrower than the queued range leaves bytes unwritten inside a range it has just @@ -1207,6 +1213,13 @@ namespace MobileGL::MG_Backend::DirectGLES { // page-substitution fast path needs a page-coverable range to engage, and below it // the driver falls back to waiting out the WAR hazard on the CPU. if (!wholeBuffer && size < kEsprytInvalidateRangeMinBytes) return 0u; + // A partial range-invalidating map must not overtake a ring copy into this store the GPU + // has not run yet. Adreno 830 answers such a map on a store with a copy still queued by + // losing the copy (dev f973008c, the pull arm's FlushPendingRangesNow: create-instancing + // drew whole Embeddium terrain sections empty, SSIM 0.870). The range goes to the ring + // instead, GPU-ordered behind the copies. The whole-buffer orphan-map is exempt: it + // rewrites every byte from the shadow the queued copies were taken from. + if (!wholeBuffer && ringCopyInFlight) return 0u; return GL_MAP_WRITE_BIT | (wholeBuffer ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_INVALIDATE_RANGE_BIT); } diff --git a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp index a8fb9009..6a1eb1b0 100644 --- a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp @@ -819,19 +819,19 @@ namespace { } // namespace TEST(EsprytFlushLadder, AWholeBufferRangeOrphansTheStore) { - EXPECT_EQ(InvalidateFlushAccessFor(0, kStore, 0, kStore, kStore, kStore), + EXPECT_EQ(InvalidateFlushAccessFor(0, kStore, 0, kStore, kStore, kStore, false), static_cast(GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)); } TEST(EsprytFlushLadder, ALargePartialRangeInvalidatesExactlyThatRange) { const SizeT start = 4096; const SizeT end = start + kEsprytInvalidateRangeMinBytes; - EXPECT_EQ(InvalidateFlushAccessFor(start, end, start, end, kStore, kStore), + EXPECT_EQ(InvalidateFlushAccessFor(start, end, start, end, kStore, kStore, false), static_cast(GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT)); } TEST(EsprytFlushLadder, ASmallPartialRangeFallsThroughToTheStagingRing) { - EXPECT_EQ(InvalidateFlushAccessFor(4096, 4096 + 64, 4096, 4096 + 64, kStore, kStore), 0u) + EXPECT_EQ(InvalidateFlushAccessFor(4096, 4096 + 64, 4096, 4096 + 64, kStore, kStore, false), 0u) << "below the threshold the map WAITS out the WAR hazard on the CPU instead of " "substituting pages, which is the whole reason tier 2 exists"; } @@ -846,19 +846,33 @@ TEST(EsprytFlushLadder, AMapWiderThanTheQueuedRangeRefusesTierOne) { const SizeT queuedStart = 4096; const SizeT queuedEnd = queuedStart + kEsprytInvalidateRangeMinBytes; // Page-aligned outward, the exact widening the in-tree note records. - EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, 0, queuedEnd + 4096, kStore, kStore), 0u) + EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, 0, queuedEnd + 4096, kStore, kStore, false), 0u) << "a widened INVALIDATE_RANGE declares bytes dead that the shadow is not about to " "rewrite, and overwrites whatever the GPU put there"; // And narrower, which is the same corruption read the other way round: bytes left // unwritten inside a range that has just been declared dead. - EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, queuedStart, queuedEnd - 8, kStore, kStore), 0u); + EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, queuedStart, queuedEnd - 8, kStore, kStore, false), + 0u); } TEST(EsprytFlushLadder, AnEmptyRangeIsNeverTierOne) { - EXPECT_EQ(InvalidateFlushAccessFor(4096, 4096, 4096, 4096, kStore, kStore), 0u); + EXPECT_EQ(InvalidateFlushAccessFor(4096, 4096, 4096, 4096, kStore, kStore, false), 0u); +} + +// dev f973008c's rule on the push arm (P8-MF): Adreno 830 loses an upload-ring copy still queued +// on a store when a partial range-invalidating map of that store overtakes it, so a partial range +// waits behind the copy on the ring. The whole-buffer orphan-map is exempt. +TEST(EsprytFlushLadder, APartialRangeDoesNotOvertakeAQueuedRingCopy) { + const SizeT start = 4096; + const SizeT end = start + kEsprytInvalidateRangeMinBytes; + EXPECT_EQ(InvalidateFlushAccessFor(start, end, start, end, kStore, kStore, true), 0u) + << "a range-invalidating map of a store with a queued ring copy loses the copy on Adreno 830"; + EXPECT_EQ(InvalidateFlushAccessFor(0, kStore, 0, kStore, kStore, kStore, true), + static_cast(GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)) + << "the whole-buffer orphan-map rewrites every byte from the shadow the copies were taken from"; } #else -// The same five names in a pull build, for the G2/G14 reason above: the ladder's push arm +// The same six names in a pull build, for the G2/G14 reason above: the ladder's push arm // (FlushPendingRangesFrom) is the only one that carries this decision as a function - the pull // arm's FlushPendingRangesNow is byte-frozen against 5cb826b0 (ID-15) and may not grow one. #define MGL_PUSH_ONLY_OR_SKIP() \ @@ -869,4 +883,5 @@ TEST(EsprytFlushLadder, ALargePartialRangeInvalidatesExactlyThatRange) { MGL_PUS TEST(EsprytFlushLadder, ASmallPartialRangeFallsThroughToTheStagingRing) { MGL_PUSH_ONLY_OR_SKIP(); } TEST(EsprytFlushLadder, AMapWiderThanTheQueuedRangeRefusesTierOne) { MGL_PUSH_ONLY_OR_SKIP(); } TEST(EsprytFlushLadder, AnEmptyRangeIsNeverTierOne) { MGL_PUSH_ONLY_OR_SKIP(); } +TEST(EsprytFlushLadder, APartialRangeDoesNotOvertakeAQueuedRingCopy) { MGL_PUSH_ONLY_OR_SKIP(); } #endif // MOBILEGL_PIPE_PUSH diff --git a/docs/Disaggregated/notes/p8/MF.md b/docs/Disaggregated/notes/p8/MF.md new file mode 100644 index 00000000..02d44086 --- /dev/null +++ b/docs/Disaggregated/notes/p8/MF.md @@ -0,0 +1,79 @@ +# P8-MF:dev 的 Adreno 830 部分冲刷修复镜像到 push 梯子 + +> 2026-09-30,分支 `p8/mf`(基线 `37174242` = `p8/main` 合入 dev `f973008c`)。简报 `~/w7/notes/p8/BRIEF-MF.md`;全文 `~/w7/notes/p8/mf-report.md`;证据(设备归档、探针日志、脚本、APK 哈希)`~/w7/notes/p8/mf-evidence/`(树外)。只动 Espryt:Magma 没有这条梯子,create-instancing × Magma 本来就是 0.999983725(MD)。 + +## 1. 改动 + +| 提交 | 位置 | 内容 | +|---|---|---| +| `ac9ab078` | `MobileGL/MG_Backend/DirectGLES/Managers.cpp:1520` | `FlushPendingRangesFrom` 的 ring 档每次 `glCopyBufferSubData` 后盖 `ringCopyRetireSerial = CurrentFrameSerial() + 1`(同 pull 臂 `Managers.cpp:1808`) | +| `ac9ab078` | `MobileGL/MG_Backend/DirectGLES/Managers.cpp:1496` | 逐 range 读 `ringCopyRetireSerial > CompletedFrameSerial()`(同 pull 臂 `:1785`),传给 `InvalidateFlushAccessFor` | +| `ac9ab078` | `MobileGL/MG_Backend/DirectGLES/Managers.h:1222` | 纯函数 `InvalidateFlushAccessFor` 新参数 `ringCopyInFlight`:部分范围且拷贝未退役 → 0(走 ring,GPU 序排在拷贝后);整 buffer 孤立映射豁免 | +| `ac9ab078` | `MobileGL/MG_Test/Buffer/SplitBufferTest.cpp:865` | `EsprytFlushLadder.APartialRangeDoesNotOvertakeAQueuedRingCopy`(pull 构建同名 skip 桩 `:886`);原 5 例补 `false` | +| `9c24a14d` | `scripts/p3a_untouched_regions.sh`、`scripts/p4a_untouched_regions.sh` | G5 第十行 `FlushPendingRangesNow` 也改为钉住行;两条梯子一起重钉在审过的修后正文:Now `c40cae66…`、From `33bc4cb8…`,提交 `ac9ab078`,裁定 ID-P8-14 | + +- pull 臂 `FlushPendingRangesNow` 未动:`37174242` 的正文 = `37da3c3a` 正文 + 恰好 `f973008c` 的那一块(dev 自己的正文另缺 feat 既有的 PipeStats 块,所以 `f973008c` 处哈希是 `c85443ce…`,不是钉值)。 +- 不盖 `DrainResidentWritesNow` 的 ring 拷贝:目标是收养的不可变 store,永不走范围失效映射(dev 同样不盖)。 + +## 2. 设备(红米 2f7cbe2e,create-instancing × Espryt,SSIM) + +| APK(lib sha256 前 8) | monolith ×3 | spawn ×3(pbuffer) | +|---|---|---| +| 基线 `37174242`(`5ed6d6dc`) | 0.870389049 ×3 | 0.999984504 ×3 | +| 修复 `9c24a14d`(`34d826c7`) | **0.999984504 ×3** | 0.999984504 ×3 | +| red-once:守卫关(`7938ff89`) | 0.870389049 ×3 | — | + +回归(Espryt monolith ×1,基线 → 修复):sodium 0.999993736 → 0.999993736;common-mods 0.999869638 → 0.999869638;improved-transparency-26.3 0.999683991 → 0.999683991。 + +## 3. 为什么 spawn 不受影响:同样撞上,但拷贝在映射前已提交 + +探针 APK(守卫关 = 基线行为,`FlushPendingRangesFrom` 每个 range 一行日志,未提交;`04e09442`),monolith / spawn 各 1 次;主机 llvmpipe 三臂同一探针数字完全一致。 + +| 量 | monolith | spawn(server) | +|---|---|---| +| SSIM | 0.870389049 | 0.999984504 | +| 冲刷的 range / store | 3340 / 11 | 12956 / 26 | +| ring 拷贝 | 3337 | 3337 | +| **部分失效映射且该 store 仍有未退役 ring 拷贝** | **2**(store 38、63,`[3518480,+217120)`、`[3279760,+209360)`) | **2**(同偏移、同大小的两个 arena) | +| 整 buffer 孤立映射 | 1 | 9617(158 MB;24 B–307 KB 的小 store,monolith 下它们不进这条梯子) | +| 该 arena 上次 ring 拷贝与部分映射之间,其他 store 的 range | 4,其中整 buffer 映射 0 | 32 / 34,其中整 buffer 映射 28 / 30 | + +- server 走的是同一条梯子:客户端的 `glCopyBufferSubData` 是 CPU 拷贝(`MobileGL/MG_State/GLState/BufferState/BufferObject.cpp:809-831`),以 `resource_subdata` 到 server,`Ops_H_SubData` 排队(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:2469`),draw 时 `EnsureBufferResourceForHandle` 冲刷(`Managers.cpp:4247`)。 +- T0 只收养了 16 MiB 持久暂存 buffer(server 日志 `T0 imported store {slot=1, gen=1} - 16777216 bytes`),3.8 MiB arena 不是 T0,不走 `Managers.cpp:2412` 的 resident 分支。 +- 机理探针(守卫关 + 每次 ring 拷贝后 `glFlush`,未提交;`754c5d64`):monolith 0.999984504。拷贝只在部分映射到来时仍未提交才会丢。 +- 结论:spawn 以同样的 tier 组合撞上同样两处,但 server 在拷贝与映射之间多做了 28–30 次其他 store 的整 buffer 孤立映射,拷贝在映射前已被提交(推断:具体是哪一步触发提交未单独验证)。spawn 是碰巧躲过;修复后 spawn 不变(0.999984504 ×3)。 + +## 4. red-once + +| 门 | 变异 | 载体 | 红 | +|---|---|---|---| +| 设备修复 | `Managers.h:1222` 守卫改 `false && …`(盖戳保留),APK `7938ff89` | create-instancing × Espryt × monolith ×3 | 0.870389049 ×3 | +| 新单测(守卫) | 同上,主机 | `EsprytFlushLadder.APartialRangeDoesNotOvertakeAQueuedRingCopy` | `SplitBufferTest.cpp:868`:得 6(WRITE\|INVALIDATE_RANGE),期望 0 | +| 新单测(整 buffer 豁免) | 守卫去掉 `!wholeBuffer &&` | 同上 | `SplitBufferTest.cpp:871`:得 0,期望 10(WRITE\|INVALIDATE_BUFFER) | +| G5 重钉 | 提交 `ac9ab078` 对旧脚本(`HEAD:` 版本) | `p3a`/`p4a` `37da3c3a HEAD` | 两脚本 rc=1:首个移动 `FlushPendingRangesNow`,另移动 `FlushPendingRangesFrom` | +| G5 From 钉值 | 新脚本对基线提交 | `p3a`/`p4a` `37da3c3a 37174242` | rc=1:首个移动 `FlushPendingRangesFrom`,并报 "IS A PINNED ROW (ID-P8-14)" | + +- 每个探针都以拷回 + 比对 + `touch` 恢复(`restored identical: True`、`git diff` 干净)。 +- G5 `--self-test`:p3a 两条梯子各有 pin-precedence 控制与一 token 变异控制,全过;p4a 10 个负控制全触发且点名。 + +## 5. 门(`gate.sh ~/w7/p8-mf p8mf`,头 `9c24a14d`) + +| 项 | 结果 | +|---|---| +| G1 | 符号增 0 减 0(clang 22.1.6);`.text` `0xa5ba43`,与本树基线构建相同 | +| fatal census / link ratchet / parity / 协议修订 | 78 / 172 不变 / 0 / 5 | +| wire-declines | 58 行,59 站点 | +| 卫生 | 14/14 OK(含 `g5_p3a`、`g5_p4a`) | +| unit | 2722 / 2722(+1 新单测) | +| split / spawn / tcp | 738 / 652 / 664 全过 | +| Magma split / spawn / tcp / full-split | 271 / 248 / 216 / 671 全过 | +| gpu monolith / inproc | 两臂各 3695 / 3699,同 4 例:`DirectGLES.Tcp.UnlocatedIoBlocks.*` 3 例 + 其 fixture `TcpServer.Start.P8aUnlocatedIoBlocks`(负载下);单独重跑两臂各 31/31 过 | +| retrace(`ctest -L retrace`,monolith + .SPLIT + .SPAWN,两后端) | 230 / 236,失败只有已知 6 例 iterationrp(主机 llvmpipe JIT) | + +## 6. 未做 / 遗留 + +| 项 | 原因 | +|---|---| +| server 对小 store 反复整 buffer 孤立映射(create-instancing 一次重放 9617 次 / 158 MB,monolith 1 次) | 性能项,不是本包;它恰好让 spawn 躲过本缺陷 | +| 其他 GPU 写(SSBO、xfb)后接部分失效映射是否同样丢 | 未观察到;守卫只覆盖 ring 拷贝(同 MD) | +| 两臂之间的注释仍写 pull 臂冻结于 `5cb826b0`、From 钉于 `3e298c9a`(`MobileGL/MG_Backend/DirectGLES/Managers.cpp:1376`、`:1535`、`:1553`;`MobileGL/MG_Test/Buffer/SplitBufferTest.cpp:877`) | ID-41 起就过时,ID-P8-14 后 pull 行也改为钉住;都在哈希正文之外,留给下次改这段的人 | diff --git a/scripts/p3a_untouched_regions.sh b/scripts/p3a_untouched_regions.sh index 98289664..d9f1839a 100644 --- a/scripts/p3a_untouched_regions.sh +++ b/scripts/p3a_untouched_regions.sh @@ -24,9 +24,19 @@ # RingAllocate the fast path on the hot upload route # FlushPendingRangesNow the three-tier drain: tier 1 is an INVALIDATE_BUFFER map + memcpy # for a whole-buffer flush or a range >= 128 KiB, tier 2 the upload -# ring + glCopyBufferSubData, tier 3 UploadRangeNow +# ring + glCopyBufferSubData, tier 3 UploadRangeNow. PINNED since +# ID-P8-14 (see below) # FlushPendingRangesFrom the SAME three-tier drain, on the arm that ships. Born in P3a # +# ID-P8-14 PINS THE TENTH TOO. dev f973008c changed FlushPendingRangesNow on purpose: on Adreno 830 a +# partial range-invalidating map of a store that still has an upload-ring glCopyBufferSubData queued +# loses the copy (create-instancing drew whole terrain sections empty, SSIM 0.870), so the ring tier +# stamps ringCopyRetireSerial and a partial range waits behind an unretired copy. P8 merged dev +# (37174242) and P8-MF mirrored the rule into FlushPendingRangesFrom (via InvalidateFlushAccessFor). +# The base ref CI passes (37da3c3a) predates the fix, so the tenth row can no longer be compared +# against ; both ladders are now PINNED rows, re-pinned together on the reviewed post-fix +# bodies, and still compared on every run. +# # THE ELEVENTH IS HERE BY INTEGRATOR DECISION ID-15, and it is the whole reason the row about the # tenth is not decorative. On this tree Managers.cpp has TWO arms: `#if MOBILEGL_PIPE_PUSH` holds # FlushPendingRangesFrom, `#else` holds FlushPendingRangesNow byte-identically, and BOTH of a push @@ -36,9 +46,10 @@ # would exit 2 rather than run). The consequence, and what ID-15 closes: a push build compiles no # FlushPendingRangesNow at all, so a gate that hashed only that name protected text the shipping # build never sees, and a tier-threshold edit made in the ladder that DOES ship would pass it. So -# both names are hashed. The pull build's ladder is compared against the base ref; the push build's -# is compared, ALWAYS AND WHETHER OR NOT DEFINES IT, against a sha pinned at the commit -# where that body was reviewed - see PINNED_FUNCTIONS. +# both names are hashed. The push build's ladder is compared, ALWAYS AND WHETHER OR NOT +# DEFINES IT, against a sha pinned at the commit where that body was reviewed - see +# PINNED_FUNCTIONS. The pull build's ladder was compared against the base ref until ID-P8-14 pinned +# it the same way (see the note under the list above). # # "ALWAYS" IS INTEGRATOR DECISION ID-41 AND IT IS A CHANGE. Until P5 this text said "pinned" while # the implementation consulted the pin only as a FALLBACK, when did not define the function @@ -50,7 +61,8 @@ # InvalidateFlushAccessFor, the pull arm (FlushPendingRangesNow) is byte-identical across that # change and G1 reports .text +0, so it is the intended change to the eleventh row rather than # drift - and the answer is to RE-PIN it, not to revert it and not to let the row stop being -# compared. The OTHER TEN stay ref-a-vs-ref-b: nothing about them moved. +# compared. The other nine stay ref-a-vs-ref-b: nothing about them moved (the tenth moved at +# ID-P8-14 and was pinned then). # # THE TENTH IS HERE BY INTEGRATOR DECISION ID-11, resolving a contradiction inside the brief. # D-F's "Decision: nine functions" table omits FlushPendingRangesNow, but BRIEF-P3A.md:420 calls it @@ -99,24 +111,33 @@ set -u -o pipefail SOURCE_PATH=MobileGL/MG_Backend/DirectGLES/Managers.cpp -# The ten that exist at the P3a base ref, so their baseline is read out of . -FUNCTIONS="IsPoolable EnrollIntoPool AcquireFromPool TrimBufferPool ClearBufferPool ProcessDeferredBufferReleases CreateRingStorage RingAvailable RingAllocate FlushPendingRangesNow" -# The ELEVENTH (ID-15), and it is a different kind of row: it was BORN in P3a, so there was no -# body at the base ref to compare it with, and its baseline is PINNED below and consulted -# UNCONDITIONALLY (ID-41 - see the long note at the top of this file). +# The nine unpinned rows: they exist at the P3a base ref, so their baseline is read out of . +FUNCTIONS="IsPoolable EnrollIntoPool AcquireFromPool TrimBufferPool ClearBufferPool ProcessDeferredBufferReleases CreateRingStorage RingAvailable RingAllocate" +# The TENTH and the ELEVENTH, and they are a different kind of row: their baseline is PINNED below +# and consulted UNCONDITIONALLY (ID-41 - see the long note at the top of this file). The eleventh +# (ID-15) was BORN in P3a, so there was no body at the base ref to compare it with; the tenth joined +# it at ID-P8-14, when a reviewed fix moved it past the base ref. PINNED_FUNCTIONS is in the fixed +# order, because apply_pinned_shas appends the pinned rows in that order. # -# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and -# writes the decision beside them; a pin with no commit and no decision next to it is a number -# nobody can audit. -# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted -# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good -# for a parameter of the flush ladder, so tier 1's widening refusal is -# live code the moment a SEG_STAGE snapshot is narrower than the queued -# range. <- CURRENT -PINNED_FUNCTIONS="FlushPendingRangesFrom" -PINNED_BASELINE_REF=3dadd4c1 -PINNED_BASELINE_DECISION=ID-41 -PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0 +# THE PIN, AND WHAT RE-PINS IT. Whoever moves one of these bodies deliberately replaces the shas +# AND the commit AND the decision, and writes a line here; a pin with no commit and no decision +# next to it is a number nobody can audit. +# 3e298c9a From 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted +# 3dadd4c1 From 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is +# good for a parameter of the flush ladder, so tier 1's widening +# refusal is live code the moment a SEG_STAGE snapshot is narrower +# than the queued range. +# ac9ab078 Now c40cae66... ID-P8-14, P8-MF: dev f973008c (merged at 37174242) keeps a partial +# From 33bc4cb8... range-invalidating map behind an upload-ring copy still queued on the +# store - Adreno 830 loses the queued copy - in FlushPendingRangesNow; +# ac9ab078 mirrors it into FlushPendingRangesFrom (the stamp on +# the ring tier, the guard in InvalidateFlushAccessFor, the +# whole-buffer exemption kept). <- CURRENT +PINNED_FUNCTIONS="FlushPendingRangesNow FlushPendingRangesFrom" +PINNED_BASELINE_REF=ac9ab078 +PINNED_BASELINE_DECISION=ID-P8-14 +PINNED_SHA_FlushPendingRangesNow=c40cae66f021160478ef9af6bcf5e8ef0bf26721486173695d5257263c97723f +PINNED_SHA_FlushPendingRangesFrom=33bc4cb855b932ed4fb0de44a4cfa7a9be8aca34da267ccd2fc9fedfad4d3328 ALL_FUNCTIONS="$FUNCTIONS $PINNED_FUNCTIONS" EXPECTED_FUNCTION_COUNT=11 # The functions the self-test perturbs, one control each. ClearBufferPool is small, has no forward @@ -353,21 +374,22 @@ apply_pinned_shas() { for name in $PINNED_FUNCTIONS; do eval "pinned=\$PINNED_SHA_$name" if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then - say "$name has no pinned baseline sha; the eleventh row cannot be compared" + say "$name has no pinned baseline sha; that row cannot be compared" return 2 fi grep -v " $name\$" "$file" > "$file.unpinned" || true mv -f "$file.unpinned" "$file" || return 2 - # Appended LAST, which is also its position in the fixed order (it is the eleventh of eleven), - # so the header's "stdout is always the sha list in the fixed order" stays true and a baseline - # captured by redirect still diffs cleanly against a two-ref run. + # Appended LAST, in PINNED_FUNCTIONS order, which is also their position in the fixed order + # (the tenth and the eleventh of eleven), so the header's "stdout is always the sha list in the + # fixed order" stays true and a baseline captured by redirect still diffs cleanly against a + # two-ref run. printf '%s %s\n' "$pinned" "$name" >> "$file" done return 0 } -# The BASELINE side (). The TEN are extracted strictly, so a rename of one of THOSE is -# exit 2 rather than a silently short list. FlushPendingRangesFrom then takes the PINNED sha +# The BASELINE side (). The NINE are extracted strictly, so a rename of one of THOSE is +# exit 2 rather than a silently short list. Each pinned row then takes the PINNED sha # WHETHER OR NOT defines it (ID-41), and a that defines it DIFFERENTLY is # reported - loudly - because the two answers disagreeing is itself a finding rather than a # reason to prefer the ref. @@ -379,7 +401,7 @@ extract_baseline() { return 2 fi if ! python3 "$PY" extract "$blob" "$FUNCTIONS" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then - say "the baseline ref '$ref' does not define the pre-P3a ten exactly once each:" + say "the baseline ref '$ref' does not define the nine unpinned rows exactly once each:" sed 's/^/[p3a-untouched] /' "$WORK_DIR/$out.err" >&2 return 2 fi @@ -387,7 +409,7 @@ extract_baseline() { eval "pinned=\$PINNED_SHA_$name" atRef=$(python3 "$PY" extract "$blob" "$name" 2>/dev/null | awk -v n="$name" '$2 == n { print $1 }') if [ -z "$atRef" ]; then - say "$name is not defined at '$ref' (it was born in P3a): its baseline is the sha PINNED in" + say "$name is not defined at '$ref' (a pinned row): its baseline is the sha PINNED in" say " this script, captured at $PINNED_BASELINE_REF ($PINNED_BASELINE_DECISION)" elif [ "$atRef" != "$pinned" ]; then say "NOTE: $name IS defined at '$ref' and hashes" @@ -517,14 +539,16 @@ if [ "${1:-}" = "--self-test" ]; then # --- THE PINNED ROW (ID-41) ----------------------------------------------------------------- # TWO more controls, and they exist because none of the five above can see the pin at all: every # one of them compares one extraction of the working tree against another, so they would all be - # green on a build of this script in which PINNED_SHA_* was never read by anything. The eleventh - # row's whole claim is "the baseline is the PIN, not ", and that claim needs its own two. + # green on a build of this script in which PINNED_SHA_* was never read by anything. A pinned + # row's whole claim is "the baseline is the PIN, not ", and that claim needs its own two - + # per pinned row (both ladders since ID-P8-14). for target in $PINNED_FUNCTIONS; do eval "pinned=\$PINNED_SHA_$target" # (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row - # which is the shape of every CI passes today, since ff2994d9 and 37da3c3a both DEFINE - # FlushPendingRangesFrom - must come out of apply_pinned_shas carrying the PIN. This is the + # FlushPendingRangesFrom and both predate ID-P8-14's FlushPendingRangesNow - must come out of + # apply_pinned_shas carrying the PIN. This is the # control that would have caught the ID-41 defect itself: before it, the pin was consulted # only when lacked the function, so the row silently reverted to ref-a-vs-ref-b the # moment a base ref had one. @@ -537,7 +561,7 @@ if [ "${1:-}" = "--self-test" ]; then got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha") if [ "$got" != "$pinned" ]; then say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as" - say " '${got:-}' and not as the pin ($pinned). The eleventh row would be compared" + say " '${got:-}' and not as the pin ($pinned). That pinned row would be compared" say " against again, which is exactly the defect $PINNED_BASELINE_DECISION closed." exit 2 fi @@ -545,7 +569,7 @@ if [ "${1:-}" = "--self-test" ]; then # (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go # red, must name the row, and must say that the row is PINNED - R-16's "a control asserts its - # OWN failure string": the pinned row and the ten ref-a rows are fixed differently, and a + # OWN failure string": the pinned rows and the nine ref-a rows are fixed differently, and a # reader who gets only the generic paragraph goes looking for a diff against that does # not exist. python3 "$PY" perturb-token "$WORK_DIR/pristine.cpp" "$WORK_DIR/pinperturbed.cpp" \ diff --git a/scripts/p4a_untouched_regions.sh b/scripts/p4a_untouched_regions.sh index 928047e6..f582b7d7 100644 --- a/scripts/p4a_untouched_regions.sh +++ b/scripts/p4a_untouched_regions.sh @@ -25,7 +25,8 @@ # CreateRingStorage glBufferStorageEXT + persistent|coherent, retires at serial + 1 # RingAvailable self-heals a stale context generation # RingAllocate the fast path on the hot upload route -# FlushPendingRangesNow the three-tier drain, pull arm +# FlushPendingRangesNow the three-tier drain, pull arm. PINNED since ID-P8-14: dev +# f973008c moved it past on purpose (the parent's note) # FlushPendingRangesFrom the SAME three-tier drain, on the arm that ships (ID-15). Born # in P3a, so its baseline is PINNED, not read from # @@ -125,6 +126,11 @@ # be RE-PINNED on the reviewed P5 body rather than reverted or quietly left comparing # against . This script says so on stderr, in both directions, and keeps the PIN - # because the pin is the reviewed text. +# +# ID-P8-14 extends the same treatment to FlushPendingRangesNow: dev f973008c changed it +# past every CI passes (the Adreno 830 queued-ring-copy guard), P8-MF mirrored the +# rule into FlushPendingRangesFrom, and both ladders were re-pinned together on the +# reviewed post-fix bodies. set -u -o pipefail # One row per region: @@. The ORDER is the fixed order the sha list is printed @@ -151,21 +157,32 @@ ShouldUseCaveatTextureFormat@function@MobileGL/MG_Backend/DirectGLES/Utils.cpp" EXPECTED_FUNCTION_COUNT=17 -# The one region born in P3a, so there is no body at P4a's base ref that this phase reviewed: its -# baseline is PINNED and consulted UNCONDITIONALLY (DEVIATIONS D-N/2, and ID-41 for the parent). +# The two flush ladders. FlushPendingRangesFrom was born in P3a, so there is no body at P4a's base +# ref that this phase reviewed; FlushPendingRangesNow was moved past the base ref by a reviewed fix +# (ID-P8-14, dev f973008c: a partial range-invalidating map must not overtake an upload-ring copy +# still queued on the store - Adreno 830 loses the copy). Both baselines are PINNED and consulted +# UNCONDITIONALLY (DEVIATIONS D-N/2, and ID-41 for the parent). # -# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and -# writes the decision beside them; a pin with no commit and no decision next to it is a number -# nobody can audit. The parent script carries the identical table and the two must not drift. -# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted -# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good -# for a parameter of the flush ladder, so tier 1's widening refusal is -# live code the moment a SEG_STAGE snapshot is narrower than the queued -# range. <- CURRENT -PINNED_FUNCTIONS="FlushPendingRangesFrom" -PINNED_BASELINE_REF=3dadd4c1 -PINNED_BASELINE_DECISION=ID-41 -PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0 +# THE PIN, AND WHAT RE-PINS IT. Whoever moves one of these bodies deliberately replaces the shas +# AND the commit AND the decision, and writes a line here; a pin with no commit and no decision +# next to it is a number nobody can audit. The parent script carries the identical table and the +# two must not drift. +# 3e298c9a From 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted +# 3dadd4c1 From 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is +# good for a parameter of the flush ladder, so tier 1's widening +# refusal is live code the moment a SEG_STAGE snapshot is narrower +# than the queued range. +# ac9ab078 Now c40cae66... ID-P8-14, P8-MF: dev f973008c (merged at 37174242) keeps a partial +# From 33bc4cb8... range-invalidating map behind an upload-ring copy still queued on the +# store - Adreno 830 loses the queued copy - in FlushPendingRangesNow; +# ac9ab078 mirrors it into FlushPendingRangesFrom (the stamp on +# the ring tier, the guard in InvalidateFlushAccessFor, the +# whole-buffer exemption kept). <- CURRENT +PINNED_FUNCTIONS="FlushPendingRangesNow FlushPendingRangesFrom" +PINNED_BASELINE_REF=ac9ab078 +PINNED_BASELINE_DECISION=ID-P8-14 +PINNED_SHA_FlushPendingRangesNow=c40cae66f021160478ef9af6bcf5e8ef0bf26721486173695d5257263c97723f +PINNED_SHA_FlushPendingRangesFrom=33bc4cb855b932ed4fb0de44a4cfa7a9be8aca34da267ccd2fc9fedfad4d3328 # The regions the self-test perturbs, one negative control each. FOUR, exactly as D-N requires, and # each is a different shape so that a control which only ever perturbed the easy one cannot leave @@ -502,8 +519,8 @@ extract_ref() { return $? } -# The BASELINE side (). The sixteen ordinary regions are extracted strictly, so a rename of -# one of THOSE is exit 2 rather than a silently short list. FlushPendingRangesFrom then takes the +# The BASELINE side (). The fifteen ordinary regions are extracted strictly, so a rename of +# one of THOSE is exit 2 rather than a silently short list. Each pinned row then takes the # PINNED sha (DEVIATIONS D-N/2) whether or not defines it, and a that defines it # DIFFERENTLY is reported - loudly - because the two answers disagreeing is itself a finding. extract_baseline() { @@ -517,7 +534,7 @@ extract_baseline() { mv -f "$WORK_DIR/$out.spec.tmp" "$WORK_DIR/$out.spec" || return 2 done if ! python3 "$PY" extract "$WORK_DIR/$out.spec" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then - say "the baseline ref '$ref' does not define the sixteen unpinned regions exactly once each:" + say "the baseline ref '$ref' does not define the fifteen unpinned regions exactly once each:" sed 's/^/[p4a-untouched] /' "$WORK_DIR/$out.err" >&2 return 2 fi @@ -608,8 +625,8 @@ compare_lists() { say " $labelA ${shaA:-}" say " $labelB ${shaB:-}" if is_pinned_row "$name"; then - # ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned row and the - # sixteen ref-a rows fail differently and are fixed differently, so a reader who sees + # ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned rows and the + # fifteen ref-a rows fail differently and are fixed differently, so a reader who sees # only the generic paragraph below goes looking for a diff against that does not # exist. say " $name IS A PINNED ROW ($PINNED_BASELINE_DECISION): its baseline is ALWAYS the sha" @@ -640,10 +657,10 @@ compare_lists() { # A gate that always says "identical" and a gate that is working produce the same green, so the # comparison has to be shown failing. Both controls run: the POSITIVE ones (an untouched copy # compares equal; an edit OUTSIDE the regions is invisible; a baseline that names another sha for -# the PINNED row is overridden by the pin) rule out a comparison that reports every region as -# moved, and the NINE NEGATIVE ones - D-N's four regions, each perturbed at the HEAD of its body -# and again at its TAIL, plus a ONE-TOKEN edit to the pinned ladder compared AGAINST THE PIN -# (ID-41) - rule out the comparison that never reports any, the extraction whose extent stops +# a PINNED row is overridden by the pin) rule out a comparison that reports every region as +# moved, and the TEN NEGATIVE ones - D-N's four regions, each perturbed at the HEAD of its body +# and again at its TAIL, plus a ONE-TOKEN edit to each pinned ladder compared AGAINST THE PIN +# (ID-41; two pinned rows since ID-P8-14) - rule out the comparison that never reports any, the extraction whose extent stops # before the closing brace (F-m2), and a pin that nothing consults. if [ "${1:-}" = "--self-test" ]; then [ $# -eq 1 ] || { say "--self-test takes no other arguments"; exit 2; } @@ -750,18 +767,17 @@ if [ "${1:-}" = "--self-test" ]; then # --- THE PINNED ROW (ID-41) ----------------------------------------------------------------- # TWO more controls, and they exist because none of the ten above can see the pin at all: every # one of them compares one extraction of the working tree against another, so they would all be - # green on a build of this script in which PINNED_SHA_* was never read by anything. The pinned + # green on a build of this script in which PINNED_SHA_* was never read by anything. A pinned # row's whole claim is "the baseline is the PIN, not " (D-N/2, ID-41), and that claim - # needs its own two. + # needs its own two - per pinned row (both ladders since ID-P8-14). for target in $PINNED_FUNCTIONS; do eval "pinned=\$PINNED_SHA_$target" targetSource=$(printf '%s\n' "$REGIONS" | awk -F@ -v n="$target" '$1 == n { print $3 }') [ -n "$targetSource" ] || { say "$target is not one of the regions"; exit 2; } # (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row - - # which is the shape of every CI passes, since 37da3c3a DOES define - # FlushPendingRangesFrom and no longer hashes the pin - must come out of apply_pinned_shas - # carrying the PIN. + # which is the shape of every CI passes, since 37da3c3a DOES define both ladders and + # hashes neither pin - must come out of apply_pinned_shas carrying the PIN. grep -v " $target\$" "$WORK_DIR/pristine.sha" > "$WORK_DIR/pinprec.sha" || true printf '%s %s\n' \ "0000000000000000000000000000000000000000000000000000000000000000" "$target" \ @@ -779,7 +795,7 @@ if [ "${1:-}" = "--self-test" ]; then # (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go # red, must name the region, and must say the region is PINNED - R-16's "a control asserts its - # OWN failure string": the pinned row and the sixteen ref-a rows are fixed differently, and a + # OWN failure string": the pinned rows and the fifteen ref-a rows are fixed differently, and a # reader who gets only the generic paragraph goes looking for a diff against that does # not exist. rm -rf "$WORK_DIR/pinperturbed"