From 13827e376f9d2c86189bfe6e0ca0f4968d66d337 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 11 Sep 2026 14:31:46 -0400 Subject: [PATCH] [Feat] (MG_Pipe): give HasLiveHostWrites its producer, decline every persistent-map acquisition under split, and retire the verify pin that was waiting for exactly this --- MobileGL/MG_Impl/Pipe/PipeFill.cpp | 9 +++++ MobileGL/MG_Pipe/PipeApply.cpp | 55 ++++++++++++++++++++++++++---- MobileGL/MG_Pipe/PipeApply.h | 17 +++++++-- 3 files changed, 72 insertions(+), 9 deletions(-) diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 38317380..3e08c604 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -702,6 +702,15 @@ namespace MobileGL::MG_Pipe { // one was emitted, so this cannot be false - but a zeroed record (null // handle, size 0) is not the answer if that pre-pass is ever relaxed. if (!MGPipeBuildSubDataRecord(handle, at, length, record, /*verbatimShadow=*/true)) return; +#if MOBILEGL_BUILD_DISAGGREGATED + // P5 (b1): the live-host-writes bit rides the content record, because + // "someone may be writing these bytes without telling you" is a fact about the + // CONTENT and not about the storage. It is set from the object's PUBLISHED + // value rather than from a live IsMapped() read so that the record and the + // edge that announced it can never disagree. MGPipeBuildSubDataRecord does not + // take the object, which is why it is set here and not in the builder. + record.HasLiveHostWrites = buffer.HasLiveHostWritesForWire() ? 1 : 0; +#endif MGPipeApplyResourceSubData(record, base + at); }); if (!encodable) { diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index 23fa782a..61cb308a 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -26,6 +26,13 @@ #include #endif +#if MOBILEGL_BUILD_DISAGGREGATED +// R-6's tier gate. One spelling, asked at the one place the decline is decided. Outside the +// MOBILEGL_PIPE_VERIFY block above on purpose: the tier is a property of the BUILD, not of the +// comparator, and a split build without the comparator still declines every acquisition. +#include +#endif + #include #include #include @@ -835,12 +842,18 @@ namespace MobileGL::MG_Pipe { return true; } -#if MOBILEGL_PIPE_VERIFY - // D-A4's pin. HasLiveHostWrites is ALWAYS false in this phase and is written by - // nobody: it exists so the phase that pushes persistent-mapped host writes can set it - // with no new record kind. A producer that landed under it would change what - // IsBufferDrawClean answers with no other visible edit, so a verify build refuses to - // let one arrive unannounced. +#if MOBILEGL_PIPE_VERIFY && !MOBILEGL_BUILD_DISAGGREGATED + // D-A4's pin, AND P5 (b1) IS THE PHASE IT WAS WAITING FOR. It said "HasLiveHostWrites + // is always false in this phase and is written by nobody: it exists so the phase that + // pushes persistent-mapped host writes can set it with no new record kind", and a + // verify build refused to let such a producer arrive unannounced. + // + // The producer is announced: MGPSubData::HasLiveHostWrites, set by + // MGPipeEmitResourceSubData from BufferObject::HasLiveHostWritesForWire and read by + // ApplyBufferWrite below. So the pin is LIFTED FOR A SPLIT BUILD ONLY, and left + // standing everywhere else - in a monolith build nothing sets the bit and the wire is + // still the thing that would say so if something started to. That is the whole value + // of the pin and it survives the phase it was written for. void PinNoLiveHostWrites(const MGPipeResourceRecord& record, MGPipeHandle res, const char* call) { if (!record.HasLiveHostWrites) return; MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeLiveHostWrites") @@ -905,6 +918,16 @@ namespace MobileGL::MG_Pipe { return false; } PinNoLiveHostWrites(*stored, record.Res, call); +#if MOBILEGL_BUILD_DISAGGREGATED + // P5 (b1): THE PRODUCER. The record's own statement about the resource, applied + // before the serial moves so that a probe re-entered from inside the backend hook + // below already sees it. It is an assignment and not an OR: the bit is a STATE, + // and a content record emitted while nothing maps the buffer is exactly how the + // state goes back to false - which is why the falling edge pushes one block + // (BufferObject::NotePersistentMapStateChanged) rather than relying on the next + // ordinary write to arrive. + stored->HasLiveHostWrites = record.HasLiveHostWrites != 0; +#endif // THE SERIAL MOVES BEFORE THE BACKEND IS TOLD, and that order is load-bearing: // the backend stamps its own synced serial from this record inside the hook, so a @@ -1932,6 +1955,26 @@ namespace MobileGL::MG_Pipe { // the flag exists to announce, and the wire is what refuses to let it arrive unnamed. PinNoLiveHostWrites(*record, handle.Handle, "map_persistent"); +#if MOBILEGL_BUILD_DISAGGREGATED + // R-6: A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION, ALWAYS. + // + // The mint returns a raw void* that the client stores as the store's base + // (BufferObject.cpp:238 / :603 / :658). Across a process boundary that address is + // meaningless, and under `inproc` it is WORSE than meaningless: it happens to work, + // so a lane that kept adoption alive would be green for a reason spawn cannot + // reproduce, and persistent-map-push - an exit-gate counter - would be structurally + // zero (PipeStats.cpp says so in as many words). Forcing T2 here rather than at the + // three client call sites is what keeps `mpr` identical between the arms: the + // roundtrip is COUNTED above, unconditionally, because a decline costs the same round + // trip as a mint. + // + // The frontend already tolerates a decline in all three places, and has since P3a. + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && + MG_Remote::Client::AdoptTierIsEmulate()) { + return nullptr; + } +#endif + // NO SERIAL MOVES and NO DESCRIPTOR CHANGES: the donation re-mints the backend's own // driver object, which is a server-local event that the backend's own id generation // already catches, and the client's view of the store's extent is untouched by it. diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index e736f06b..bfd409bf 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -219,9 +219,20 @@ namespace MobileGL::MG_Pipe { // sub-data). It is what replaces the frontend change serial the backend used to // mirror, and no MGPipe call may require the client to provide or know one. Uint64 Serial = 0; - // ALWAYS FALSE IN P3a, AND WRITTEN BY NOBODY. It exists so the phase that pushes - // persistent-mapped host writes can set it with zero new record kinds; a verify build - // pins that it is false, so that phase cannot land a silent semantic change under it. + // "DOES THIS RESOURCE HAVE A LIVE HOST WRITER RIGHT NOW?" + // + // False and written by nobody through P3a and P4a; P5 (b1) is the phase it was waiting + // for and gives it its producer, with zero new record kinds exactly as planned: the + // bit rides MGPSubData::HasLiveHostWrites - a byte out of that payload's existing pad - + // and ApplyBufferWrite assigns it here. In a MONOLITH build nothing sets it and the + // MOBILEGL_PIPE_VERIFY wire (PinNoLiveHostWrites) still refuses a producer, so the pin + // survives the phase it was written for instead of being deleted by it. + // + // It is what IsBufferDrawCleanByHandle asks under split INSTEAD of the frontend + // object's IsMapped(), because a spawned server has no frontend object to ask. Getting + // that substitution wrong once already cost a silent regression - an emulated + // persistent map read draw-clean for ever - which MG_Test/SanityTest.cpp's + // DirectGLESBufferDrawProbe pair now pins from both sides. Bool HasLiveHostWrites = false; // ---- P4a. Only a record of kind Texture ever carries these; a buffer's stay at