From e9b3f5fe04e887024ccbed51a41981a13f3b821a Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 22 Sep 2026 21:11:46 -0400 Subject: [PATCH] [MG_Impl, MG_IntegrationTest, ci] (P7): the compare-at-read hook gets a negative control of its own - MGPipeVerifyReadHook honours MOBILEGL_PIPE_VERIFY_CORRUPT: it perturbs the ORACLE after CopyField, and AGAIN after the ID-49 neutral-pack overwrite, because that overwrite replaces the pack half wholesale and CorruptStorage perturbs an array's element 0 - which is the pack half - until now the knob only ever reached EntryCompare, so every red it could produce was the client thread's where=entry and the hook that runs on the server's apply thread at every backend read had no falsifier at all - new entry per backend, DirectGLES/DirectVulkan.VerifySplitReadCorrupted., with CORRUPT=GetPixelStoreParameters and VERIFY_FATAL=0 - the field is in the kReadback fill mask, so the client entry compare would otherwise abort the process before the server reached the read - the case reads the SERVER role's half of its private log alone: with the knob armed a union search over both halves would be satisfied by the arm that is not under test - PipeStatsWindow.h gains ReadServerLogSince for that - test.yml grows the matching control step - run the two entries, then re-read both server halves from outside for the where=read report - measured: 2/2 green, server halves 3 and 2 lines, client halves 0 lines; revert-and-run with the two corruption blocks removed gives 2/2 red and both server halves 0 lines - the lane goes 1070 to 1072 --- .github/workflows/test.yml | 38 ++++++++ MobileGL/MG_Impl/Pipe/PipeFill.cpp | 20 +++++ MobileGL/MG_IntegrationTest/CMakeLists.txt | 22 +++++ .../Harness/PipeStatsWindow.h | 14 +++ .../Scenarios/PipeVerifyArmingScenario.cpp | 87 +++++++++++++++++++ MobileGL/MG_Remote/CONTRACT-P7.md | 2 +- .../notes/p65/validation-status.md | 2 +- docs/Disaggregated/notes/p7/verify-split.md | 29 +++++-- 8 files changed, 206 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 707702eb..33a69729 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1056,6 +1056,44 @@ jobs: fi echo "the corrupted field turned ${matched} selected split entries red, as it must" + # NEGATIVE CONTROL A ON THE OTHER COMPARATOR (P7 wave 3, V1). The step above proves the + # ENTRY compare - client thread, verb boundary, `where=entry`. The compare-at-read hook runs + # on the SERVER's apply thread at every backend read and is the only comparator that arm ever + # runs, and until MG_Impl/Pipe/PipeFill.cpp's hook honoured MOBILEGL_PIPE_VERIFY_CORRUPT it + # had no control at all: a hook that had stopped comparing was indistinguishable from a hook + # with nothing to report. + # + # NOT THE "ctest MUST FAIL" SHAPE, and the reason is mechanical rather than a preference: + # GetPixelStoreParameters is in kReadback's fill mask, so with MOBILEGL_PIPE_VERIFY_FATAL at + # its default the client's entry compare aborts the process at the ReadPixels boundary and + # the server never reaches the read. The entries therefore pin FATAL=0, survive their own + # divergence, and each asserts from inside the process that the SERVER role's half of its + # private log carries the report. This step re-reads those logs from outside, so the claim + # does not rest only on the assertion that produced them. + # + # THE GREEN HALF is the lane run above: the same hook, the same window, with no corruption + # armed, across 1070 entries at zero divergences. + - name: Negative control A (split, read side) - the server's own read must report it + working-directory: build-verify-split + env: + MOBILEGL_ITEST_REQUIRE_GPU: "1" + run: | + FILTER='(DirectGLES|DirectVulkan)\.VerifySplitReadCorrupted\.' + matched=$(ctest -N -L '^integration-verify-split$' -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:') + if [ "${matched}" -lt 2 ]; then + echo "::error::split read-side control A selected ${matched} tests; its filter no longer matches both backends" + exit 1 + fi + ctest --output-on-failure -L '^integration-verify-split$' -R "${FILTER}" --no-tests=error + for backend in DirectGLES DirectVulkan; do + log="MobileGL/MG_IntegrationTest/pipe-verify-split-read-corrupt-${backend}.server.log" + if ! grep -q 'Fatal{PipeVerifyDiffer, "GetPixelStoreParameters@ReadPixels".*where=read}' "${log}"; then + echo "::error::${log} carries no compare-at-read report for the corrupted pack field: the hook is not comparing on the server's apply thread, or the ID-49 neutral-pack window swallowed the perturbation" + exit 1 + fi + done + echo "both backends reported GetPixelStoreParameters@ReadPixels where=read in the SERVER half, as they must" + # NEGATIVE CONTROL B ON THE SPLIT ARM, WITH THE SPLIT ARM'S PAIR. The monolith pair # (GenerateMipmap:GetActiveTextureUnit) is inert here - the split backend's generate_mipmap # resolves its texture by the verb's handle and never reads the active unit - so this step diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 4153cc66..8f7f584d 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -722,6 +722,17 @@ namespace MobileGL::MG_Pipe { // never recurses (and never reports the inner read against a half-copied scratch). g_verify.InHook = true; MGPipeFillAccess::CopyField(g_readScratch, *ctx, field); + // P7 wave 3 (V1): NEGATIVE CONTROL A REACHES THIS ARM TOO. Until this line the knob only + // ever perturbed EntryCompare's snapshot, so every red it could produce said `where=entry` + // and was produced on the CLIENT thread - which left the compare-at-read hook, the arm + // that runs on the server's apply thread and does the whole of the split lane's per-read + // work, with no falsifier at all: a hook that had stopped comparing would have looked + // exactly like a hook with nothing to report. The perturbation goes on the ORACLE, for + // EntryCompare's reason: the arm under test is the stored value, so corrupting THAT would + // be testing the corruption. + if (g_verify.Corrupt && *g_verify.Corrupt == field) { + MGPipeApplyVerifyCorruption(g_readScratch, field); + } const Bool equal = MGPipeInputsFieldEqual(field, self, g_readScratch); g_verify.InHook = false; if (equal) return; @@ -737,6 +748,15 @@ namespace MobileGL::MG_Pipe { } return true; }); + // AND THE CONTROL IS RE-APPLIED, because the overwrite above replaces the pack half + // WHOLESALE - the corruption included, since CorruptStorage perturbs an array's FIRST + // ELEMENT and the pack half is element 0. Without this line the one window in which + // this arm is most load-bearing (the server's read_pixels, 8530 reads over 220 of the + // lane's entries) would be the one window MOBILEGL_PIPE_VERIFY_CORRUPT= + // GetPixelStoreParameters cannot turn red. + if (g_verify.Corrupt && *g_verify.Corrupt == field) { + MGPipeApplyVerifyCorruption(g_readScratch, field); + } const Bool equalToTheNeutralPack = MGPipeInputsFieldEqual(field, self, g_readScratch); g_verify.InHook = false; if (equalToTheNeutralPack) return; diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 32b5ee8b..3e714936 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -3967,6 +3967,28 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND MOBILEGL_PIPE_VERIFY) ENVIRONMENT "${mglItestVerifyArmEnv}\;MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters\;MOBILEGL_PIPE_VERIFY_FATAL=0\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-split-corrupt-${mglItestVerifyBackend}.log" RESOURCE_LOCK "pipe-verify-split-corrupt-${mglItestVerifyBackend}.log" ) + # A ON THE OTHER ARM (P7 wave 3, V1). Control A above perturbs EntryCompare's snapshot, + # which runs on the CLIENT thread at a verb boundary and reports `where=entry`; the + # compare-at-read hook, which runs on the SERVER's apply thread at every backend read and + # is the only comparator that arm ever runs, had no control at all until this entry. The + # knob names GetPixelStoreParameters because ReadPixels is where the hook does its one + # piece of arm-specific reasoning - the ID-49 neutral pack window, whose oracle it rewrites + # - so a control aimed anywhere else would leave exactly that branch unproven. + # + # FATAL=0 for the corrupt entry's reason and one more: this field IS in kReadback's fill + # mask (FillPoints.def), so the client's entry compare fires first and with FATAL at its + # default the process would die before the server reached the read. The case reads the + # SERVER role's half of its private log and nothing else. + gtest_discover_tests(MobileGLIntegrationTest + TEST_PREFIX "${mglItestVerifyBackend}.VerifySplitReadCorrupted." + TEST_FILTER "PipeVerifyArmingScenario.CorruptedFieldIsReportedOnTheServerRead" + DISCOVERY_TIMEOUT 30 + PROPERTIES + LABELS "integration-verify-split" + TIMEOUT ${MGL_ITEST_VERIFY_TIMEOUT} + ENVIRONMENT "${mglItestVerifyArmEnv}\;MOBILEGL_PIPE_VERIFY_CORRUPT=GetPixelStoreParameters\;MOBILEGL_PIPE_VERIFY_FATAL=0\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-verify-split-read-corrupt-${mglItestVerifyBackend}.log" + RESOURCE_LOCK "pipe-verify-split-read-corrupt-${mglItestVerifyBackend}.log" + ) # B (G5): the child brings up its own inproc session from the inherited environment, and # PoisonOmissionScenario's ReadChildLog reads BOTH role halves of the child's log, because # under a transport the poisoned read can fire on either side of the seam. diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h b/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h index daa6a512..c7cd2439 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h +++ b/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h @@ -147,6 +147,20 @@ namespace MGITest::PipeStatsWindow { inline std::string ReadLaneLog() { return ReadLaneLogSince(LogMark{}); } + // THE SERVER HALF ALONE, for a claim whose whole content is WHICH ROLE said it. The + // concatenation above answers "did anyone report this", which is the right question for a + // diagnostic that could honestly come from either side; it is the wrong question for a + // control that exists to prove the SERVER's apply thread still runs the comparator, because + // the client's own entry compare reports the same field on the same verb and would satisfy a + // union search all by itself. Empty in a pull build (there is no server half), which a caller + // must read as "could not look" rather than as "the server said nothing". + inline std::string ReadServerLogSince(const LogMark& mark) { +#if MOBILEGL_BUILD_DISAGGREGATED + MGPipeSyncPeerLog(); +#endif + return ReadFileSince(ServerLibraryLogPath(), mark.server); + } + // The last summary line in the log, verbatim. `found` is false when the library never emitted // one, which is a different failure from "the counter read zero" and has to be reported as // one: it means the stats channel never reached the process, not that the workload did diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp index d2df7877..066f4799 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp @@ -25,6 +25,15 @@ // perturbed before the entry compare, so a comparator that works must // report Fatal{PipeVerifyDiffer, "@"}. A comparator that // compares nothing stays quiet and this case goes red. +// CorruptedFieldIsReportedOnTheServerRead +// - the same control aimed at the OTHER comparator (P7 wave 3, V1). +// The entry compare runs on the client thread at a verb boundary; the +// compare-at-read hook runs on the server's apply thread at every +// backend read, and until this case existed nothing could tell a hook +// that had stopped comparing from a hook with nothing to say. It +// reads the SERVER role's half of the log, because with the knob +// armed the client reports the same field and a union search would be +// satisfied by the arm that is not under test. // // The observable is the library's own log, because MG_Config is not reachable from this module // (on Android it links the SHIPPING libMobileGL.so, built -fvisibility=hidden) and the arming @@ -265,5 +274,83 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } << appended; } + // NEGATIVE CONTROL A ON THE OTHER ARM (P7 wave 3, V1): the COMPARE-AT-READ hook, on the + // SERVER's apply thread, inside the ID-49 neutral pack window. + // + // WHY THE CASE ABOVE DOES NOT COVER IT. MOBILEGL_PIPE_VERIFY_CORRUPT used to perturb only + // EntryCompare's snapshot, so every red it could produce came from the CLIENT thread at a + // verb boundary and said `where=entry`. The hook that runs on every backend read - the + // whole of the split lane's per-read work, and the only comparator the server's apply + // thread ever runs - had no falsifier: a hook that had stopped comparing looked exactly + // like a hook with nothing to report, and the lane would have been just as green. + // + // WHY THIS FIELD AND THIS VERB. GetPixelStoreParameters@ReadPixels is where the hook does + // its one piece of ARM-SPECIFIC reasoning (PipeFill.cpp's + // ServerReadsInsideTheNeutralPackWindow): the applier reads the backend with the neutral + // pack, so inside that window the oracle for the pack half is the neutral pack rather than + // the live context. A control aimed anywhere else would leave exactly that branch unproven. + // + // THE SERVER HALF, NOT THE UNION. With this knob armed the client's own entry compare + // reports `GetPixelStoreParameters@...` too, in the client's log, so a whole-lane search + // would be satisfied without the server having run anything at all. The assertion below + // reads the server role's half and nothing else. + // + // MOBILEGL_PIPE_VERIFY_FATAL=0, for CorruptedFieldIsReported's reason and one more: the + // client's entry compare on the ReadPixels verb fires FIRST (kReadback's fill mask carries + // this field, FillPoints.def), so with FATAL at its default the process would abort before + // the server ever reached the read. + TEST_F(PipeVerifyArmingScenario, CorruptedFieldIsReportedOnTheServerRead) { + if (!Ready()) return; + + constexpr const char* kPackField = "GetPixelStoreParameters"; + const char* knob = std::getenv("MOBILEGL_PIPE_VERIFY_CORRUPT"); + if (knob == nullptr || std::string(knob) != kPackField) { + GTEST_SKIP() << "this case is the compare-at-read hook's negative control and needs " + "MOBILEGL_PIPE_VERIFY_CORRUPT=" << kPackField << " for the whole " + "process, which is what the VerifySplitReadCorrupted. ctest entries " + "set; no other field is read by the server inside a window whose " + "oracle the hook rewrites"; + } + if (AmbientQuirkFromEnvironment("MOBILEGL_PIPE_VERIFY") != AmbientQuirk::On) { + GTEST_SKIP() << "MOBILEGL_PIPE_VERIFY_CORRUPT is set but MOBILEGL_PIPE_VERIFY is not, so " + "the comparator is dormant and there is nothing to corrupt"; + } + if (LibraryLogPath().empty()) { + GTEST_SKIP() << "MOBILEGL_LOG_FILE_PATH is not set, so the library has nowhere to report " + "the divergence; the VerifySplitReadCorrupted. ctest entries set both"; + } + if (MGITest::PipeStatsWindow::ServerLibraryLogPath().empty()) { + GTEST_SKIP() << "this build writes no server-role log, so there is no half in which the " + "server's own report could be told apart from the client's; the hook's " + "read arm is registered on the split (inproc) lane only"; + } + + const MGITest::PipeStatsWindow::LogMark before = LibraryLogMark(); + ASSERT_NO_FATAL_FAILURE(DrawOneFrame()); + + const std::string server = MGITest::PipeStatsWindow::ReadServerLogSince(before); + const std::string expected = + std::string(kDifferPrefix) + ", \"" + kPackField + "@ReadPixels\""; + const std::size_t at = server.find(expected); + ASSERT_NE(at, std::string::npos) + << "MOBILEGL_PIPE_VERIFY_CORRUPT=" << kPackField << " perturbs the hook's ORACLE at " + "every backend read of that field, and the server's read_pixels reads it under " + "the server's own stamp - so the server role's log had to carry " << expected + << ", ...}. It carried nothing: either the compare-at-read hook is not running on " + "the apply thread, or the neutral-pack window swallowed the perturbation, and " + "in both cases every per-read green in this lane is green for no reason. Server " + "half of the log appended by this case:\n" + << server; + + const std::size_t eol = server.find('\n', at); + const std::string line = + server.substr(at, eol == std::string::npos ? std::string::npos : eol - at); + EXPECT_NE(line.find("where=read"), std::string::npos) + << "the server half reported the corrupted field, but not from the compare-at-read " + "arm - `where=` says which comparator spoke, and only `read` is this case's " + "subject. Line:\n" + << line; + } + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_Remote/CONTRACT-P7.md b/MobileGL/MG_Remote/CONTRACT-P7.md index 3fbb1f4a..755eeed8 100644 --- a/MobileGL/MG_Remote/CONTRACT-P7.md +++ b/MobileGL/MG_Remote/CONTRACT-P7.md @@ -102,7 +102,7 @@ P7 收官 = 五道出口门同时成立(§2–§7),而不是路线图 P7 ## §6 出口门 2:verify 零分歧 -verify 今天只在 monolith 注册(`MG_IntegrationTest/CMakeLists.txt:1684-1760`)。门 2 = **verify × {monolith, split(inproc)} × Magma 零分歧**,且两条既有负控(`VERIFY_CORRUPT`、`POISON_OMIT`)在 split 臂上各红一次。前置债:verify+split 撞旧 `PipeRespecifyScope` 断言(`notes/p65/validation-status.md:118`)——放宽为「按 level 的 producer 各自覆盖其宣告范围」,正反单测各一。trace 侧:8 个 verify case × DirectVulkan × inproc 一轮,分歧计数 0。**状态(wave 3 V1,`p7/verify-split`,`notes/p7/verify-split.md`)**:前置债已清(`70f86064`),`integration-verify-split` 双后端 1070/1070 绿、两条负控在 split 臂上各红一次(POISON_OMIT 用 split 臂自己的 `ReadPixels:GetPixelStoreParameters` 配对)、trace 侧 8 case 分歧 0,门 2 在主机上成立;唯一的真实分歧是 server `read_pixels` 的 ID-49 中性 pack 窗口,按预言而非规则处理,spawn 不在门内也无法测(比对器是同进程仪器)。 +verify 今天只在 monolith 注册(`MG_IntegrationTest/CMakeLists.txt:1684-1760`)。门 2 = **verify × {monolith, split(inproc)} × Magma 零分歧**,且两条既有负控(`VERIFY_CORRUPT`、`POISON_OMIT`)在 split 臂上各红一次。前置债:verify+split 撞旧 `PipeRespecifyScope` 断言(`notes/p65/validation-status.md:118`)——放宽为「按 level 的 producer 各自覆盖其宣告范围」,正反单测各一。trace 侧:8 个 verify case × DirectVulkan × inproc 一轮,分歧计数 0。**状态(wave 3 V1,`p7/verify-split`,`notes/p7/verify-split.md`)**:前置债已清(`70f86064`),`integration-verify-split` 双后端 1072/1072 绿、两条负控在 split 臂上各红一次(POISON_OMIT 用 split 臂自己的 `ReadPixels:GetPixelStoreParameters` 配对)、trace 侧 8 case 分歧 0,门 2 在主机上成立;唯一的真实分歧是 server `read_pixels` 的 ID-49 中性 pack 窗口,按预言而非规则处理,spawn 不在门内也无法测(比对器是同进程仪器)。 --- diff --git a/docs/Disaggregated/notes/p65/validation-status.md b/docs/Disaggregated/notes/p65/validation-status.md index 7c939c95..d5c64111 100644 --- a/docs/Disaggregated/notes/p65/validation-status.md +++ b/docs/Disaggregated/notes/p65/validation-status.md @@ -104,7 +104,7 @@ TCP harness 的 fork pre-flight 也曾先占用远端单会话,使真正测试 这是既有 verify-only “全资源 respecify”断言与后来合法 per-level producer 的冲突, 不是 TCP 前送失败。证据 `/home/swung/p65-lf-verify/` 与 `/home/swung/p65-verify-controls/`; 此项尚未宣称通过。 -(P7 wave 3 V1 更新:断言已放宽为「按 level 的 producer 覆盖其宣告范围」,verify × split 车道 1070/1070,见 [`notes/p7/verify-split.md`](../p7/verify-split.md)。) +(P7 wave 3 V1 更新:断言已放宽为「按 level 的 producer 覆盖其宣告范围」,verify × split 车道 1072/1072,见 [`notes/p7/verify-split.md`](../p7/verify-split.md)。) CI 的 `runtime_mode_proof.py` 曾读取无角色后缀的日志名,导致真实两后端用例通过后仍 FileNotFoundError。 修复为读取 client/server 两份,并保留任一缺失为硬失败。除了正反 self-test, diff --git a/docs/Disaggregated/notes/p7/verify-split.md b/docs/Disaggregated/notes/p7/verify-split.md index 922b572e..cd3c64c2 100644 --- a/docs/Disaggregated/notes/p7/verify-split.md +++ b/docs/Disaggregated/notes/p7/verify-split.md @@ -90,8 +90,8 @@ upload,所以描述符宣告 `(target, level)` 而调用递空指针,会走 (3) 仅 DirectGLES 半边的四条 Magma decline 用例(§2.3 发现 B)。GLES 的 texture-view 用例照 monolith verify 车道的拼法带 `MOBILEGL_ESPRYT_ENABLE_TEXTURE_VIEW=1` 另注册一次。 - 手工注册(机制过滤器把它们一起拿掉了):每后端 `VerifySplitArming.`(arming 断言,私有日志)、 - `VerifySplitCorrupted.` / `VerifySplitPoisonOmitted.`(§3)、`VerifySplit.PoisonOmissionScenario.WithoutOmissionCompletes` - (负控 B 的 CI 靶子)。 + `VerifySplitCorrupted.` / `VerifySplitReadCorrupted.` / `VerifySplitPoisonOmitted.`(§3)、 + `VerifySplit.PoisonOmissionScenario.WithoutOmissionCompletes`(负控 B 的 CI 靶子)。 - **不设 `MGITEST_SPLIT_LANE=1`**:它武装 ScenarioFixture 析构的「本用例必须推动过 client 编码器序号」,对整二进制 集合是错的(tier 3 实测 13 红全是 CapsMirror 回答的查询)。取而代之的是**逐条目私有日志** (`SplitLogPaths.cmake.in` 的 `MGL_VERIFY_SPLIT_TEST_LISTS`)和 CI 的逐条目臂证明:每个 client 日志都必须有 @@ -167,9 +167,10 @@ DirectVulkan 半边保留且绿。 | 车道 | 臂 | 基线 `3c2867d3` | 本包之后 | 结果 | |---|---|---|---|---| | `integration-verify` | monolith,DirectGLES / DirectVulkan | 568 / 568 = 1136(本配置下 2 红,§2.4) | 568 / 568 = **1136**(名不变) | 1136/1136,271 skip | -| `integration-verify-split` | inproc,DirectGLES / DirectVulkan | 0 / 0(不存在) | 533 / 537 = **1070** | 1070/1070,117 skip(GLES 60、Magma 57) | +| `integration-verify-split` | inproc,DirectGLES / DirectVulkan | 0 / 0(不存在) | 534 / 538 = **1072** | 1072/1072,117 skip(GLES 60、Magma 57) | -DirectGLES 533 = 530 环境 + Arming + Corrupted + PoisonOmitted;DirectVulkan 537 = 534 + 3;两者差 4 = §2.3。 +DirectGLES 534 = 530 环境 + Arming + Corrupted + ReadCorrupted + PoisonOmitted;DirectVulkan 538 = 534 + 4; +两者差 4 = §2.3。(V1 修复轮把 `VerifySplitReadCorrupted.` 加进来,每后端 +1,1070 → 1072。) --- @@ -188,6 +189,22 @@ DirectGLES 533 = 530 环境 + Arming + Corrupted + PoisonOmitted;DirectVulkan 2 × MGPipe: Fatal{PipeVerifyDiffer, "GetRenderStateParameters@DrawArrays", verb=1, where=entry} ``` +**A′(G4,读臂;V1 修复轮补)** 入口比对跑在 **client 线程的动词边界**上,报 `where=entry`;**compare-at-read +hook 跑在 server 的 apply 线程上**,是这条臂每次 backend 读唯一运行的比对器,而在本轮之前 `VERIFY_CORRUPT` +**根本到不了它**——一个停止比对的 hook 和一个没东西可报的 hook 长得一模一样。修复:hook 在 +`CopyField(g_readScratch, …)` 之后按旋钮扰动**预言**,并在 ID-49 中性 pack 覆写之后**再扰动一次**(覆写是整 +个 pack 半边的,连扰动一起抹掉——`CorruptStorage` 扰的正是数组的第 0 个元素,也就是 pack 半边)。新条目 +`{DirectGLES,DirectVulkan}.VerifySplitReadCorrupted.`(`MOBILEGL_PIPE_VERIFY_CORRUPT=GetPixelStoreParameters`、 +`FATAL=0`,因为该字段在 `kReadback` 填充掩码里、client 的入口比对会先死),用例只读**server 半边**: + +``` +[mgl-srv-apply/FATAL]: MGPipe: Fatal{PipeVerifyDiffer, "GetPixelStoreParameters@ReadPixels", verb=6, where=read} +``` + +本机实测:**2/2 绿**,server 半边 DirectGLES 3 行 / DirectVulkan 2 行,client 半边 **0 行**(入口比对在这个 +字段上不报,所以这条红线**只能**来自读臂)。反证(把 hook 里的两处扰动去掉、重编、再跑、再还原):两条条目 +**2/2 红**,两个 server 半边 `PipeVerifyDiffer` **各 0 行**。 + **B(G5)** `MOBILEGL_PIPE_POISON_OMIT=ReadPixels:GetPixelStoreParameters` 打 `(DirectGLES|DirectVulkan)\.VerifySplit\.PoisonOmissionScenario\.WithoutOmissionCompletes`:**2/2 红**,子进程 server 半边日志: @@ -196,7 +213,7 @@ server 半边日志: [mgl-srv-apply/FATAL]: MGPipe: Fatal{UnmigratedPipeInput, "GetPixelStoreParameters@ReadPixels"} ``` -正形条目 `VerifySplitCorrupted.` / `VerifySplitPoisonOmitted.` 两后端 4/4 绿。 +正形条目 `VerifySplitCorrupted.` / `VerifySplitReadCorrupted.` / `VerifySplitPoisonOmitted.` 两后端 6/6 绿。 ### 3.2 发现 C:monolith 的 POISON_OMIT 配对在 split 臂上是哑的 @@ -273,7 +290,7 @@ divergences, zero unmigrated reads` 与 `MGPipe split: DirectVulkan trans | `^integration-spawn$` | 219/219 | | `^integration-tcp$` | 222/222 | | `^integration-verify$`(monolith) | 1136/1136 | -| `^integration-verify-split$` | **1070/1070** | +| `^integration-verify-split$` | **1072/1072** | | `^integration-magma-split$` / `-spawn$` / `-tcp$` | 93/93 · 72/72 · 74/74 | 以上 lane 均在 `build-split`(verify 构建)上跑,比基线口径更严(每个 backend 读都过比对器的 hook)。