mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Impl, MG_Remote, MG_Test] (Disaggregated): P5e ra2 - a barriered fill must be quiescent, not merely about to park
- ID-132's finding, diagnosed: with the flip thrown the 70 red lane entries are ONE race
and two debts. The race is the client's residual fill writing gPipeInputs while the apply
thread is inside an earlier UNBARRIERED record. Measured interleaving, verbatim:
`[MobileGLIntegra/ERROR] RA2-PROBE fill verb=DrawElements wireop=81 waitclass=-1
applierInside=1` then `[mgl-srv-apply/ERROR] RA2-PROBE unfresh field=IsCapabilityEnabled
verb=DrawElements serverStamped=0 ownership=RECORD-SUPPLIED` then
`[mgl-srv-apply/FATAL] MGPipe: Fatal{UnmigratedPipeInput, "IsCapabilityEnabled@DrawElements"}`.
The GL thread's MGPipeServerClearVerbBoundary withdrew the applier's own stamp mid-record.
- The marker's verb is the CLIENT's verb, which is what identifies the writer without a
probe at all: the applier can only ever put a verb of MGP_VERB_OP_LIST into the block, and
40 of the 54 unbracketed aborts named a verb that is not in it.
- A/B control on the same binary and the same records: MOBILEGL_IPC_RUN_AHEAD=0 is 8/8 green
where the default arm is 2/6 red, so the defect is in the wait rule and not in a record.
- RefusePipeInputsTouchWhileApplierOwnsIt could not fire on any of it: its run-ahead arm read
`if (isBarrieredFill) return;`, and that sentence is a claim about the FUTURE ("this thread
is about to park behind the record"). The order at the validate point is fill, then emit,
then park. So the arm now tests the fact - `isBarrieredFill && !ApplyThreadIsInsideApplier()`
- and the fill sites MAKE it true first with §2.5's forced wait (QuiesceApplierBeforeFill).
- TWICE PER VERB, because the block is written in two phases that straddle publication: the
serial bump / stamp withdrawal / verb rename run before the fourteen emitters and the
63-field walk of step 4 runs after them, and the records those emitters published are
records whose apply reads the block.
- ClientVerbIsBarriered: MGP_VERB_OP_LIST joins the whole draw family through one row
(DrawVbo -> DrawArrays), so inverting it verb-first answered kOpCount for DrawElements and
eighteen other draw verbs, and the "unknown verb answers barriered" default filled for each
while the record it published was a kWaitNone draw_vbo it never parked behind. The family's
row is applied to the family; every other unknown verb keeps the conservative answer, which
the wait above now makes honest. Measured alone it is worth 112 -> 133 isplit; with the wait
in place the lane is 161 either way, so this half is the PERFORMANCE half - without it every
indexed draw takes a forced drain and run-ahead is lockstep by another name on the draw path.
- Red-once A: put the guard's arm back to `if (isBarrieredFill) return;` and
RemoteGuards.ClientBarrieredFillWhileTheApplierIsInsideUnderRunAheadIsFatalByName exits 0
instead of aborting (`Value of: DiedOfAbort(child) Actual: false`), while its green control
keeps passing. Red-once B: delete the kDraw fallback and
RemoteRunAhead.AnIndexedDrawVerbIsUnbarrieredAndTouchesPipeInputsNotAtAll dies of signal 6
with `Fatal{RoleViolation, "gPipeInputs"} - the GL thread touched gPipeInputs
(MGPipeValidateForVerb) on a RUN-AHEAD session in a barriered fill that did not first wait
for the applier to catch up`.
- Both new cases run in the ordinary unit lane with run-ahead armed by the fixture, which is
what ID-132(1) asks for: the strict lane runs under lockstep and cannot see this class.
- Gate on the flipped head: unit 2256/2256, isplit 161/181 (was 112), gpu 1339/1359 (was 1290),
gens rc=0, flavours rc=0. The remaining 20 are two named debts, not this race: 18 are
`GetBufferBindingSlot@DrawArrays [UNBARRIERED]` on the two indirect multi-draw tiers, whose
retiring phase is P8, and 2 are ID-82's own refusal of client vertex arrays.
- The arm is stated and not inferred (ID-81): WaitForApplyToCatchUp's first test is
`m_runAheadArmed && m_started`, which reduces to Transport != Monolith. Under lockstep,
under monolith and under MOBILEGL_IPC_RUN_AHEAD=0 every line added here is a call that
returns, so those arms are unchanged.
This commit is contained in:
@@ -2297,6 +2297,10 @@ TEST(RemoteGuards, ClientPipeInputsTouchOutsideABarrieredFillUnderRunAheadIsFata
|
||||
|
||||
// ... and the control: the barriered fill says so and is let through. Without this the case
|
||||
// above would pass just as well against "abort unconditionally".
|
||||
//
|
||||
// P5e (ra2): THIS CONTROL IS NOW ALSO HALF OF A PAIR, and the half it does NOT state is the
|
||||
// one that cost the flip its lane. "The applier is not inside" is true here because nothing in
|
||||
// this child ever entered it; the case below is the same call with that one fact reversed.
|
||||
TEST(RemoteGuards, ClientPipeInputsTouchInsideABarrieredFillUnderRunAheadIsAllowed) {
|
||||
const auto child = RunInChild([] {
|
||||
StartRunAheadSession();
|
||||
@@ -2307,6 +2311,69 @@ TEST(RemoteGuards, ClientPipeInputsTouchInsideABarrieredFillUnderRunAheadIsAllow
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// ---- P5e (ra2): THE FLIP'S OWN RED-ONCE, AND THE CLASS THE STRICT LANE CANNOT SEE ----------
|
||||
//
|
||||
// The strict lane runs under LOCKSTEP - ApplyOne stamps every record barriered - so nothing
|
||||
// that happens only once the client stops waiting is visible to it, by construction (ID-132).
|
||||
// This pair is what measures it instead, and it runs in the ordinary unit lane on any head,
|
||||
// flip thrown or not, because StartRunAheadSession arms run-ahead itself.
|
||||
//
|
||||
// WHAT IT PINS: `isBarrieredFill` is the caller's sentence "this touch is the residual fill of
|
||||
// a record this thread is about to park behind". That is a claim about the FUTURE. The order at
|
||||
// the validate point is fill, then emit, then park, so at the instant of the write the apply
|
||||
// thread is still draining the UNBARRIERED records the client ran ahead of - and the exemption
|
||||
// that took the claim on trust made this guard unfireable on precisely the class it exists for.
|
||||
// Measured on the flipped head: the GL thread bumped CurrentVerbSerial, withdrew the server's
|
||||
// stamp and renamed m_currentVerb underneath a record the applier was inside, and the applier
|
||||
// aborted with `Fatal{UnmigratedPipeInput, "<field>@<the CLIENT's verb>"}` - a verb no applier
|
||||
// stamp can produce, which is what identifies the writer.
|
||||
//
|
||||
// THE PROBE IS THE GUARD CALL ITSELF (ID-102): the flag is raised by the same raw entry point
|
||||
// PipeApplier's ScopedApplierEntry uses, on the client thread, BEFORE the call under test - so
|
||||
// what aborts is this probe and not some object built on the wrong thread.
|
||||
//
|
||||
// THE RED: in ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt put the run-ahead arm back
|
||||
// to `if (isBarrieredFill) return;` and this case stops aborting, while the control above keeps
|
||||
// passing - which is the difference between a rule and an exemption.
|
||||
TEST(RemoteGuards, ClientBarrieredFillWhileTheApplierIsInsideUnderRunAheadIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartRunAheadSession();
|
||||
ClientSession::NoteApplyThreadEnteredApplier();
|
||||
ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt("ra-test-surface",
|
||||
/*isBarrieredFill=*/true);
|
||||
ClientSession::NoteApplyThreadLeftApplier();
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"gPipeInputs\"}");
|
||||
}
|
||||
|
||||
// ---- P5e (ra2): AND THE OTHER HALF - AN INDEXED DRAW IS AN UNBARRIERED VERB ----------------
|
||||
//
|
||||
// MGP_VERB_OP_LIST joins the whole draw family through ONE row, `DrawVbo -> DrawArrays`, so
|
||||
// inverting it verb-first answers kOpCount for DrawElements and for every other indexed /
|
||||
// instanced / multi / indirect verb. The "unknown verb answers barriered" default then made the
|
||||
// client FILL for each of them while never parking, because the wait is decided per record and
|
||||
// the record is a draw_vbo (kWaitNone). On the flipped lane that was 21 of the 69 red entries.
|
||||
//
|
||||
// The case states it where it is decidable without a GL context: the guard runs before the
|
||||
// validate point's null-context return, so a verb that answers UNBARRIERED never reaches the
|
||||
// guard at all and the child exits 0 even with the applier flag up.
|
||||
//
|
||||
// THE RED: delete the kDraw fallback in ClientVerbIsBarriered (PipeFill.cpp) and DrawElements
|
||||
// answers barriered again - the fill runs, the guard above it sees the raised flag, and this
|
||||
// case aborts with Fatal{RoleViolation, "gPipeInputs"} instead of exiting 0.
|
||||
TEST(RemoteRunAhead, AnIndexedDrawVerbIsUnbarrieredAndTouchesPipeInputsNotAtAll) {
|
||||
const auto child = RunInChild([] {
|
||||
StartRunAheadSession();
|
||||
ClientSession::NoteApplyThreadEnteredApplier();
|
||||
MGPipeValidateForVerb(MGPipeVerb::DrawElements);
|
||||
ClientSession::NoteApplyThreadLeftApplier();
|
||||
MGPipeLeaveVerb();
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectChildSuccess(child);
|
||||
}
|
||||
|
||||
// §3.3's RED-ONCE, and the one that turns the strict lane into a gate. The probe is the
|
||||
// sticky forward itself, run on the apply thread inside a server-stamped verb with the
|
||||
// current record marked UNBARRIERED - which is the state the sink puts that thread in for
|
||||
|
||||
Reference in New Issue
Block a user