mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Backend, docs] (P7): a transport with no record arm stops by name instead of dying mid-frame
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, "<field>@<verb>"} 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.
This commit is contained in:
@@ -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, "<field>@<verb>"}, 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@<verb>\"} 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;
|
||||
|
||||
@@ -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, "<field>@<verb>"}` 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@<verb>"} 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.
|
||||
|
||||
Reference in New Issue
Block a user