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.