From 834a8917ff364aaeff29b6ab18405d716be9a285 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 22 Sep 2026 16:20:59 -0400 Subject: [PATCH] [MG_Backend, docs] (P7): a transport with no record arm stops by name instead of dying mid-frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P3b/P4b espryt D1 slice 5, landing under the P7 program (notes/p7/PLAN-PH-P34B-P7.md §1.2 row "g_fboTextureSyncList", §3 wave 2-D). ResolveFramebufferSubsystemArm and its three siblings classify a family's arm as Handles, Legacy or NoArm and stop only on NoArm. Under an active transport Legacy is not a slower answer, it is a postponed crash: all four pre-handle arms reach frontend state through accessors that are BARRIER_PULLED rows (FieldOwnership.def), and under a transport that state belongs to a client that is no longer parked behind this apply, so the accessor's honest answer is Fatal{UnmigratedPipeInput, "@"} on the first verb that reaches it. That is the audit's own claim about g_fboTextureSyncList - "separation-safe, because the record arm returns first" - which holds only while the record arm is ARMED, and nothing checked that. Each of the four now stops, under #if MOBILEGL_BUILD_DISAGGREGATED, when the transport is not monolith and the verdict is not Handles, through one shared StopOnTransportWithoutRecordArm(bit, detail) that formats both causes and hands them to BufferImpl::StopOnArmlessPipeSubsystem - this file's existing single voice for "the configuration left no arm to run". A transport with the record arm off IS that condition with a different cause: the legacy arm is present but unreachable. Reusing the voice keeps the census's family word and puts the cause in the message rather than in a new word the census would have to learn. Said precisely, this is a RESOLVE-TIME stop and not a startup one. The four resolvers are function-local statics resolved lazily at first use, deliberately - Managers.cpp's own comment says a stop inside eglMakeCurrent reads to the harness as "no usable GPU" and skips - so the stop still lands in a scenario body where ctest reports it. What it no longer does is wait for a verb to dereference the client's state and then blame the accessor. Red-once (R-16), and it is the safety argument the audit asserted but never ran: MOBILEGL_PIPE_PUSH=0x1dff (the Handles lane's 0x1fff minus 0x200, kMGPipeSubsystemFramebuffer) with MOBILEGL_TRANSPORT=inproc, running ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels. Both runs abort; what changes is what the abort says. Without the stop: Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"} - mid-frame, naming an accessor and a verb, with nothing pointing at the mask. With it: Fatal{PipeLegacyMemosDisabled, "MOBILEGL_TRANSPORT is not monolith and kMGPipeSubsystemFramebuffer (bit 9) is clear (or refused), so the record arm is off and the legacy arm is unreachable: ..."}. Gates: integration-split 202/202, integration-spawn 118/118, integration-tcp 121/121, ctest -L unit 2408/2408, integration-magma-split 73/73, spawn_lane_parity / fatal_census (79 sites, 0 unmarked) / link_ratchet (186, unchanged) green, G1 .text 0xa52203 with both nm lists byte-identical - the four blocks are inside MOBILEGL_BUILD_DISAGGREGATED and the pull build compiles none of them. --- MobileGL/MG_Backend/DirectGLES/Managers.cpp | 78 +++++++++++++++++++++ docs/Disaggregated/notes/p34b/espryt-d1.md | 55 ++++++++++++++- 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 7112972f..0cb77673 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -4239,6 +4239,40 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } +#if MOBILEGL_BUILD_DISAGGREGATED + // THE SAFETY ARGUMENT P4a MADE AND NEVER RAN (P3b/P4b espryt D1 slice 5). + // + // "Running the legacy arm" is a real answer in a monolith and a POSTPONED CRASH under a + // transport. All four pre-handle arms reach frontend state through accessors that are + // BARRIER_PULLED rows (FieldOwnership.def) - GetFramebufferBindingSlot is the loudest of + // them - and under an active transport that state belongs to a client that is no longer + // parked behind this apply. The accessor's honest answer is + // Fatal{UnmigratedPipeInput, "@"}, raised on the first verb that reaches it. + // + // So the verdict "Legacy" under a transport is not safe, it is merely quiet. The audit + // that called g_fboTextureSyncList "separation-safe because the record arm returns first" + // was right only while the record arm is ARMED, and nothing checked that. + // + // A FRAME-TIME FATAL IS THE WORST SPELLING OF "MOBILEGL_PIPE_PUSH LEFT BIT 9 CLEAR": it + // arrives after a session is up, inside a scenario body, naming an accessor and a verb + // rather than the mask that chose them. Stop at resolve time instead - once, before the + // first verb - where both causes are still in scope and can be said in one sentence. + // + // THROUGH StopOnArmlessPipeSubsystem AND NOT A SECOND FATAL, deliberately: it is this + // file's one voice for "the configuration left no arm to run", the census already knows + // its word, and a transport with no record arm IS that condition - the legacy arm is + // present but unreachable, which is the same "no arm" with a different cause. The cause + // is what `detail` carries. + [[noreturn]] void StopOnTransportWithoutRecordArm(const char* bit, const char* detail) { + char what[512]; + std::snprintf(what, sizeof(what), + "MOBILEGL_TRANSPORT is not monolith and %s is clear (or refused), so the " + "record arm is off and the legacy arm is unreachable: %s", + bit, detail); + BufferImpl::StopOnArmlessPipeSubsystem(what); + } +#endif + // The release-build VOICE for StateBackendObjectRegistry::GetOrCreateByHandle's two // silent refusals, shared by the kinds P4a re-keys so the wording cannot drift between // them. It is the shape GetOrCreateBufferResourceForHandle gives P3a's buffer family, @@ -4347,6 +4381,18 @@ namespace MobileGL::MG_Backend::DirectGLES { "MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemFramebuffer (bit 9) clear (or refuses " "it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle g_fboSynced* arm"); } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + StopOnTransportWithoutRecordArm( + "kMGPipeSubsystemFramebuffer (bit 9)", + "the pre-handle g_fboSynced* / g_fboTextureSyncList arm reads the framebuffer " + "binding slots, and under a transport GetFramebufferBindingSlot is a BARRIER_PULLED " + "row belonging to a client that is not parked behind this apply - so the first verb " + "to reach it would raise Fatal{UnmigratedPipeInput, " + "\"GetFramebufferBindingSlot@\"} mid-frame instead"); + } +#endif const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles; MGLOG_D("MGPipe: Espryt framebuffer family runs the %s arm", enabled ? "handle" : "legacy"); return enabled; @@ -4403,6 +4449,16 @@ namespace MobileGL::MG_Backend::DirectGLES { "refuses it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables the pre-handle texture " "cheap-gate trio"); } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + StopOnTransportWithoutRecordArm( + "kMGPipeSubsystemTextureResources (bit 10)", + "the pre-handle texture cheap-gate trio resolves through the frontend texture " + "objects and their binding slots, which under a transport are client memory (rule " + "E) rather than anything this side owns"); + } +#endif const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles; MGLOG_D("MGPipe: Espryt texture-resource family runs the %s arm", enabled ? "handle" : "legacy"); return enabled; @@ -4440,6 +4496,17 @@ namespace MobileGL::MG_Backend::DirectGLES { "it) and MOBILEGL_PIPE_LEGACY_MEMOS=0 disables UnitSamplerLookupMemo's WeakPtr " "arm and SamplerPassMemo's raw-pointer rows"); } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + StopOnTransportWithoutRecordArm( + "kMGPipeSubsystemSamplers (bit 11)", + "UnitSamplerLookupMemo's WeakPtr arm and SamplerPassMemo's raw-pointer rows are " + "keyed on FRONTEND objects, and under a transport there is no frontend object on " + "this side to key on - a WeakPtr that can never lock is not a slower arm, it is a " + "unit that samples through whatever it last held"); + } +#endif const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles; MGLOG_D("MGPipe: Espryt sampler family runs the %s arm", enabled ? "handle" : "legacy"); return enabled; @@ -4458,6 +4525,17 @@ namespace MobileGL::MG_Backend::DirectGLES { "MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemPrograms (bit 12) clear and " "MOBILEGL_PIPE_LEGACY_MEMOS=0 disables g_programTwinLookupMemo"); } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + verdict != BufferImpl::PipeSubsystemArmVerdict::Handles) { + StopOnTransportWithoutRecordArm( + "kMGPipeSubsystemPrograms (bit 12)", + "g_programTwinLookupMemo is keyed on the frontend ProgramObject and the legacy arm " + "reads the current program through it; under a transport the program arrives as a " + "ShaderCso handle with its archive beside the record and there is no frontend " + "program here at all"); + } +#endif const Bool enabled = verdict == BufferImpl::PipeSubsystemArmVerdict::Handles; MGLOG_D("MGPipe: Espryt program family runs the %s arm", enabled ? "handle" : "legacy"); return enabled; diff --git a/docs/Disaggregated/notes/p34b/espryt-d1.md b/docs/Disaggregated/notes/p34b/espryt-d1.md index 33cd77d7..20ccb00c 100644 --- a/docs/Disaggregated/notes/p34b/espryt-d1.md +++ b/docs/Disaggregated/notes/p34b/espryt-d1.md @@ -224,4 +224,57 @@ was re-run clean at 121/121.)* ## slice 5 — four transport-aware resolvers -`RESULT: (pending)` +**The hole.** `ResolveFramebufferSubsystemArm` and its three siblings +(`ResolveTextureResourceSubsystemArm`, `ResolveSampler…`, `ResolveProgram…`) classify a family's +arm as `Handles`, `Legacy` or `NoArm`, and stop only on `NoArm`. Under a transport `Legacy` is not +a slower answer, it is a **postponed crash**: all four pre-handle arms reach frontend state through +accessors that are BARRIER_PULLED rows (`FieldOwnership.def`), and under an active transport that +state belongs to a client that is no longer parked behind this apply — so the accessor's honest +answer is `Fatal{UnmigratedPipeInput, "@"}` on the first verb that reaches it. + +This is the audit's own claim about `g_fboTextureSyncList` — "separation-safe, because the record +arm returns first" — which is true **only while the record arm is armed**, and nothing checked +that. The row is in the plan (§1.2, `g_fboTextureSyncList`) precisely because the argument had +never been run. + +**The fix.** Each of the four, under `#if MOBILEGL_BUILD_DISAGGREGATED`, now stops when +`MG_Config::Transport != Monolith && verdict != Handles`, through one shared +`StopOnTransportWithoutRecordArm(bit, detail)` that formats both causes and hands them to +`BufferImpl::StopOnArmlessPipeSubsystem` — this file's existing single voice for "the +configuration left no arm to run". A transport with the record arm off *is* that condition with a +different cause: the legacy arm is present but unreachable. Reusing the voice keeps the census's +family word (`PipeLegacyMemosDisabled`) and the cause travels in the message rather than in a new +word the census would have to learn. + +**Red-once (R-16).** `MOBILEGL_PIPE_PUSH=0x1dff` (the Handles lane's `0x1fff` minus `0x200`, +`kMGPipeSubsystemFramebuffer`) with `MOBILEGL_TRANSPORT=inproc`, running +`ClearThenReadPixelsScenario.ClearWithNoDrawIsVisibleToDefaultFramebufferReadPixels`. Both runs +abort (exit 134) — what changes is *what the abort says*: + +*without the stop (fix reverted):* +``` +Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@Clear"} +``` +a mid-frame Fatal naming an accessor and a verb, from which nothing points at the mask. + +*with it:* +``` +Fatal{PipeLegacyMemosDisabled, "MOBILEGL_TRANSPORT is not monolith and +kMGPipeSubsystemFramebuffer (bit 9) is clear (or refused), so the record arm is off and the +legacy arm is unreachable: the pre-handle g_fboSynced* / g_fboTextureSyncList arm reads the +framebuffer binding slots, and under a transport GetFramebufferBindingSlot is a BARRIER_PULLED +row belonging to a client that is not parked behind this apply - so the first verb to reach it +would raise Fatal{UnmigratedPipeInput, "GetFramebufferBindingSlot@"} mid-frame instead"} +``` + +**"Resolve time", not "startup"** — said precisely, because the difference matters and the plan's +wording ("启动期具名 stop") is looser than the tree. The four resolvers are function-local statics +resolved lazily at first use, deliberately (`Managers.cpp`'s own comment: a stop inside +`eglMakeCurrent` reads to the harness as "no usable GPU" and SKIPS). So the stop still lands in a +scenario body where ctest reports it. What it no longer does is wait for a verb to dereference the +client's state and then blame the accessor. + +**Gates.** `integration-split` 202/202, `integration-spawn` 118/118, `integration-tcp` 121/121, +`ctest -L unit` 2408/2408, `integration-magma-split` 73/73, parity / census (79 sites, 0 unmarked) +/ ratchet (186) green. G1 `.text` `0xa52203`, both nm lists byte-identical — the four blocks are +inside `MOBILEGL_BUILD_DISAGGREGATED` and the pull build compiles none of them.