diff --git a/CMakeLists.txt b/CMakeLists.txt index bcc22001..73c81954 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -656,7 +656,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED) # MarkGpuWritten producers are on the server's side of the line, and the # block-granularity persistent-map push that tier T2 makes mandatory. MobileGL/MG_Remote/Client/GpuWritePending.cpp - MobileGL/MG_Remote/Client/PersistentMapTracker.cpp + # P13 W5: the adoption tier's verdict (the tracker itself moved to the record arm below). + MobileGL/MG_Remote/Client/AdoptTierChoice.cpp MobileGL/MG_Remote/Server/ServerSession.cpp MobileGL/MG_Remote/Server/PipeApplier.cpp MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -694,6 +695,8 @@ if (MOBILEGL_BUILD_RECORD_ARM) # arms and the probe that measures the first against the second, shared by the # renderer's bring-up and the Vulkan POST. MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp + # P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2). + MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp ) endif() diff --git a/MobileGL/MG_IntegrationTest/CMakeLists.txt b/MobileGL/MG_IntegrationTest/CMakeLists.txt index 83642f12..92a991b8 100644 --- a/MobileGL/MG_IntegrationTest/CMakeLists.txt +++ b/MobileGL/MG_IntegrationTest/CMakeLists.txt @@ -1964,14 +1964,14 @@ endif() # healthy pull build into a link error, which is why this decision is made here. if (MOBILEGL_BUILD_DISAGGREGATED) mgl_itest_probe_for_symbol(MGL_ITEST_PERSISTENT_MAP_TRACKER - "${MGL_ITEST_ROOT}/MobileGL/MG_Remote/Client" "IsLivePersistentMap") + "${MGL_ITEST_ROOT}/MobileGL/MG_State/GLState/BufferState" "IsLivePersistentMap") if (MGL_ITEST_PERSISTENT_MAP_TRACKER) message(STATUS "Integration tests: package b1's persistent-map tracker is present " "(${MGL_ITEST_PERSISTENT_MAP_TRACKER}) - PersistentCoherentMapScenario's " "membership assertion is live") target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_PERSISTENT_MAP_TRACKER=1) else() - message(STATUS "Integration tests: no source under MobileGL/MG_Remote/Client names " + message(STATUS "Integration tests: no source under MobileGL/MG_State/GLState/BufferState names " "IsLivePersistentMap - package b1's tracker has not landed, so " "PersistentCoherentMapScenario's membership assertion SKIPS") endif() diff --git a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp index 6e6fc196..58c52d43 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp @@ -23,7 +23,7 @@ // build that never compiles MG_Remote, so keying on it would turn a healthy pull build into a // link error. #if defined(MGITEST_PERSISTENT_MAP_PEEK_LIVE) && defined(MGITEST_PERSISTENT_MAP_TRACKER) -#include +#include #define MGITEST_PERSISTENT_MAP_TRACKER_LIVE 1 #endif @@ -94,7 +94,7 @@ namespace MGITest { MobileGL::MG_State::GLState::BufferObject* buffer = FrontendBuffer(bufferName); if (buffer == nullptr) return false; *outLive = static_cast( - MobileGL::MG_Remote::Client::PersistentMapTracker::IsLivePersistentMap(*buffer)); + MobileGL::MG_Record::PersistentMapTracker::IsLivePersistentMap(*buffer)); return true; #else (void)bufferName; diff --git a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.h b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.h index 942ac03e..117cea86 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.h +++ b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.h @@ -50,7 +50,7 @@ namespace MGITest { // has to be made before the compiler sees it rather than by __has_include. bool PersistentMapTrackerAvailable(); - // MG_Remote::Client::PersistentMapTracker::IsLivePersistentMap() for this buffer - b1-v1.md + // MG_Record::PersistentMapTracker::IsLivePersistentMap() for this buffer - b1-v1.md // 4.1 item 2. The membership set is meant to be exactly the early-out chain of // SyncPersistentMappedRange; asking it here is what makes a drift between the two fail in a // named test rather than silently stop the push. diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index bab2675b..52d6551d 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -36,11 +36,13 @@ // DEFINED below, so MG_Backend names no MG_Remote symbol of its own to reach Session::Fail. P13 W5: // in every build, for the record arm's fail / latch half. #include +#include #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 +#include +#include // P5c (ct), CONTRACT-P5C.md §6 layer 2: MGPipeApplierReset's role guard asks // ServerLoop::OnApplyThread() - the one predicate that tells the GL thread from the thread // that owns g_applier under an active transport - and ClientSession::Active(), which is what @@ -182,6 +184,14 @@ namespace MobileGL::MG_Pipe { MGPipeRecordLatchHook MGPipeRecordLatchHookInstalled() { return g_recordLatchHook; } MGPipeRecordLatchedHook MGPipeRecordLatchedHookInstalled() { return g_recordLatchedHook; } + // P13 W5: the client session's device-loss answer (PipeClientSeam.h). Constant-initialized for + // the same static-init reason as the hooks above. + namespace { + constinit MGPipeClientDeviceLostProbe g_clientDeviceLostProbe = nullptr; + } // namespace + void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe) { g_clientDeviceLostProbe = probe; } + Bool MGPipeClientDeviceLost() { return g_clientDeviceLostProbe != nullptr && g_clientDeviceLostProbe(); } + #if MOBILEGL_BUILD_RECORD_ARM // ---------------------------------------------------------------------------- // THE BACKEND-SIDE SESSION-FAIL SEAM (P7 wave 0). PipeSessionFail.h holds the argument; diff --git a/MobileGL/MG_Pipe/PipeClientSeam.h b/MobileGL/MG_Pipe/PipeClientSeam.h new file mode 100644 index 00000000..cc9b3238 --- /dev/null +++ b/MobileGL/MG_Pipe/PipeClientSeam.h @@ -0,0 +1,25 @@ +// MobileGL - MobileGL/MG_Pipe/PipeClientSeam.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// P13 W5: THE CLIENT SESSION'S ANSWERS THE RECORD ARM ASKS FOR, WITHOUT NAMING MG_Remote. The record +// arm's client half (the persistent-map tracker, the verb port) runs in a library without a +// transport too; there the answers are the monolith's (no session, so nothing was lost). With +// MG_Remote linked, ClientSession.cpp installs the real probe at static init. + +#pragma once + +#include + +namespace MobileGL::MG_Pipe { + + // Has the calling process's client session latched a lost device (ClientSession::DeviceLost)? + using MGPipeClientDeviceLostProbe = Bool (*)(); + void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe); + Bool MGPipeClientDeviceLost(); + +} // namespace MobileGL::MG_Pipe diff --git a/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp b/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp new file mode 100644 index 00000000..804698bb --- /dev/null +++ b/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp @@ -0,0 +1,49 @@ +// MobileGL - MobileGL/MG_Remote/Client/AdoptTierChoice.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "AdoptTierChoice.h" + +#include +#include +#include + +namespace MobileGL::MG_Remote::Client { + Bool AdoptTierIsEmulate() { + const Uint32 tier = MG_Config::Ipc.AdoptTier; + if (tier == 2) return true; + // P11 B2: UNSET (T0 by default) reaching the T2 path - settled or not - is T2, quietly: a + // session-free caller (a unit case, a harness) runs the P5 default it always ran, and a + // settled session already counted its fallback at the handshake or its first map_persistent. + if (tier == MG_Config::kAdoptTierUnset) return true; + // P11 A1 (CONTRACT-P11 §1): THE VERDICT IS THE HANDSHAKE'S, NOT THIS CALL'S. Both roles + // settle the tier from the knob and the data plane before the first record + // (Transport/AdoptTier.cpp): a stream refuses T0/T1 by name and runs T2, a client over + // shared segments dies by name there, and a server over shared segments serves the T2 + // its client settled. So a settled session reaching this with 0/1 is a T2 session and + // says nothing more - the one line was the handshake's. + if (tier <= 1 && Transport::AdoptTierSettledAtHandshake()) return true; + // A map_persistent NO handshake settled (a session-free caller) is still a NAMED + // refusal rather than a silent T2: falling back would make `MOBILEGL_IPC_ADOPT_TIER=0` + // look like a working T0 run and silently produce pmap bytes it must not produce. + // Anything above 2 is not a tier at all. + // `dl` (CONTRACT-P6 5.2): the family word every death here carries. + if (tier <= 1) { + SessionFail(MGFatalFamily::UnimplementedAdoptTier, + "MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u " + "reached a map_persistent that no handshake settled; T0 and T1 are not " + "implemented and only T2 (emulate) runs.", + static_cast(tier), static_cast(tier)); + } else { + SessionFail(MGFatalFamily::UnimplementedAdoptTier, + "MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is " + "not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the " + "P5 default).", + static_cast(tier), static_cast(tier)); + } + } +} // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/AdoptTierChoice.h b/MobileGL/MG_Remote/Client/AdoptTierChoice.h new file mode 100644 index 00000000..c9315d4f --- /dev/null +++ b/MobileGL/MG_Remote/Client/AdoptTierChoice.h @@ -0,0 +1,30 @@ +// MobileGL - MobileGL/MG_Remote/Client/AdoptTierChoice.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// P13 W5: the adoption tier's verdict at map_persistent, split out of PersistentMapTracker when the +// tracker left MG_Remote for the record arm (it runs without a transport; this does not). + +#pragma once + +#include + +namespace MobileGL::MG_Remote::Client { + // R-6's tier gate, read at use. True for MOBILEGL_IPC_ADOPT_TIER=2, the only implemented + // tier. The tiers are design/07's: T0 = the client allocates an AHardwareBuffer and the + // server imports it (P11 package B), T1 = the server exports an opaque fd (closed), T2 = + // decline and push. 0 and 1 are SETTLED AT THE HANDSHAKE (P11 A1, Transport/AdoptTier.h): + // a stream refuses them by name and runs T2, a client over shared segments dies by name + // there. So a settled session answers true here; only a map_persistent no handshake + // settled is still a named refusal. It is asked + // by MGPipeApplyMapPersistent, which is where the decline is decided, so the client's + // three adoption call sites keep their existing "null means declined" branch and the + // map-persistent-roundtrips counter keeps counting ATTEMPTS in both arms (E3(c) asserts + // mpr is equal between the monolith and the split arm, which is only true if the decline + // happens after the count, on the applier's side of the emission). + Bool AdoptTierIsEmulate(); +} // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index 84841e5b..f0acf261 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -11,6 +11,7 @@ #include "ClientSession.h" #include +#include #include #include @@ -24,7 +25,7 @@ #include "../Transport/InProcessTransport.h" #include "WireTables.h" #include "EmitTables.h" -#include "PersistentMapTracker.h" +#include #include "ServerProbe.h" #include @@ -646,6 +647,16 @@ namespace MobileGL::MG_Remote::Client { return context != nullptr && context->IsFromEndedWireSession(); } + // P13 W5: the record arm's client half (the persistent-map tracker) asks this through MG_Pipe's + // client seam, which has no MG_Remote to name. Registered at static init: the tracker may ask + // before any session starts, and must get this answer, not the seam's monolith default. + namespace { + [[maybe_unused]] const bool g_deviceLostProbeRegistered = [] { + MG_Pipe::MGPipeSetClientDeviceLostProbe(&ClientSession::DeviceLost); + return true; + }(); + } // namespace + String ClientSession::LastLossReason() { const std::lock_guard lock(LossReasonMutex()); return LossReason(); @@ -757,7 +768,7 @@ namespace MobileGL::MG_Remote::Client { "the reset serial, persistent maps) start over", static_cast(SessionsStarted())); ForgetWireTablesOfEndedSession(); - PersistentMapTracker::Instance().ForgetEndedSession(); + MG_Record::PersistentMapTracker::Instance().ForgetEndedSession(); MG_Pipe::MGPipeForgetEndedSession(); } diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index 716dc78f..418373c1 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -32,7 +32,7 @@ #include "ClientSession.h" #include "GpuWritePending.h" -#include "PersistentMapTracker.h" +#include #include "../Server/ServerLoop.h" #include "../Server/PipeApplier.h" @@ -237,7 +237,7 @@ namespace MobileGL::MG_Remote::Client { // push still has to run - a coherent map is read by the GPU on any of them - but there // is no shader that could write one, so no mark walk. void BeforeReadOnlyVerb() { - PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); MG_Pipe::MGPipeDrainDeferredDestroys(); } @@ -717,7 +717,7 @@ namespace MobileGL::MG_Remote::Client { const void* clientIndices, Uint64 clientIndexBytes, const MG_Pipe::MGPDrawIndirect* indirect) { VerbChannel session = ChannelFor(slot); - PersistentMapTracker::Instance().PushDrawConsumers(); + MG_Record::PersistentMapTracker::Instance().PushDrawConsumers(); MG_Pipe::MGPipeDrainDeferredDestroys(); // Snapshot before marking THIS draw's potential GPU writes: resolving // a GPU-produced EBO here must not clear its pending mark for this draw. @@ -1491,7 +1491,7 @@ namespace MobileGL::MG_Remote::Client { // slot would inherit a call site that was already correct. void EmitDispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) { VerbChannel session = ChannelFor("DispatchCompute"); - PersistentMapTracker::Instance().PushDrawConsumers(); + MG_Record::PersistentMapTracker::Instance().PushDrawConsumers(); MarkGpuWritesForDispatch(); MG_Pipe::MGPGridInfo record{}; @@ -1510,7 +1510,7 @@ namespace MobileGL::MG_Remote::Client { void EmitDispatchComputeIndirect(GLintptr indirect) { VerbChannel session = ChannelFor("DispatchComputeIndirect"); - PersistentMapTracker::Instance().PushDrawConsumers(); + MG_Record::PersistentMapTracker::Instance().PushDrawConsumers(); MarkGpuWritesForDispatch(); MG_Pipe::MGPGridInfo record{}; diff --git a/MobileGL/MG_Remote/Server/ServerLoop.h b/MobileGL/MG_Remote/Server/ServerLoop.h index 0d6cc0d6..b61658aa 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.h +++ b/MobileGL/MG_Remote/Server/ServerLoop.h @@ -90,7 +90,7 @@ namespace MobileGL::MG_Remote::Server { // --------------------------------------------------------------------------------- // // OnApplyThread() is the predicate under EVERY role guard on both hot paths - - // RefuseLegacyBufferArmFromApplyThread in BufferObject's accessors, PersistentMapTracker:: + // RefuseLegacyBufferArmFromApplyThread in BufferObject's accessors, MG_Record::PersistentMapTracker:: // OnServerRole, ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt, RunsAsTheServerRole, // MGPipeRefuseAllocatorFromApplyThread, RefuseLegacyTextureArmFromApplyThread. simpleperf at // head 56a77348 (FCL + Minecraft, VD12, Adreno 830) measured it at 2.66% self / 3.64% diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index a1ba7d09..161866a7 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -35,7 +35,8 @@ #include // R-6's tier gate, and the ONE spelling of it (b1's file, unchanged by this package): the // MapPersistent arm below asks it the same question MGPipeApplyMapPersistent asks. -#include +#include +#include #include #include #include diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index 1f1872c0..b9c924f6 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -13,11 +13,13 @@ #include #include #if MOBILEGL_BUILD_DISAGGREGATED -// The client role's persistent-map tracker. MG_State reaching into MG_Remote/Client is the -// layering ARCHITECTURE.md:575 names - the CLIENT role IS MG_State plus MG_Impl - and the -// edge exists only in a build that has the transport at all. +// MG_State reaching into MG_Remote/Client is the layering ARCHITECTURE.md:575 names - the CLIENT +// role IS MG_State plus MG_Impl - and the edge exists only in a build that has the transport at all. #include -#include +#endif +#if MOBILEGL_BUILD_RECORD_ARM +// The client role's persistent-map tracker (P13 W5: the record arm's, so beside this file). +#include #include #include @@ -60,8 +62,8 @@ namespace MobileGL::MG_State::GLState { // of MG_Config::Transport and OnServerRole() is now one relaxed load of the apply // thread's key (ServerLoop.h), so the monolith answer costs a load each. void RefuseLegacyBufferArmFromApplyThread(const char* accessor) { - if (!MG_Remote::Client::PersistentMapTracker::PushIsArmed()) return; - if (!MG_Remote::Client::PersistentMapTracker::OnServerRole()) return; + if (!MG_Record::PersistentMapTracker::PushIsArmed()) return; + if (!MG_Record::PersistentMapTracker::OnServerRole()) return; FatalLegacyBufferArmFromApplyThread(accessor); } #endif @@ -97,7 +99,7 @@ namespace MobileGL::MG_State::GLState { // tracker is a leaked singleton whose entries are raw pointers, and an entry that // outlives its object is the one failure this set must not have. Forget is a no-op // for a buffer that was never a member. - MG_Remote::Client::PersistentMapTracker::Instance().Forget(*this); + MG_Record::PersistentMapTracker::Instance().Forget(*this); #endif // P3a D-L: the buffer's death crosses as resource_destroy, which is the catalogue // call for it - no seventh NotifyStateObjectDestroyed raiser is added, because that @@ -419,11 +421,11 @@ namespace MobileGL::MG_State::GLState { // overwrite the server shadow through the monolith adapter without crossing the wire - // and "do nothing" is spelled Fatal, not `return`, because a server-side caller here // is a role violation and not a shape the design tolerates. - if (MG_Remote::Client::PersistentMapTracker::PushIsArmed()) { - if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) { + if (MG_Record::PersistentMapTracker::PushIsArmed()) { + if (MG_Record::PersistentMapTracker::OnServerRole()) { FatalLegacyBufferArmFromApplyThread("SyncPersistentMappedRange"); } - MG_Remote::Client::PersistentMapTracker::Instance().PushBlocksFor(*this); + MG_Record::PersistentMapTracker::Instance().PushBlocksFor(*this); return; } #endif @@ -447,7 +449,7 @@ namespace MobileGL::MG_State::GLState { // push does, and a second route to the same record is a second thing to keep in step. if (size == 0) return; // Assert ownership at the final producer entry too, before serial/aggregate updates. - if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) { + if (MG_Record::PersistentMapTracker::OnServerRole()) { MGLOG_F("MGPipe: Fatal{RoleViolation, \"PushMappedSpanBlock\"} - the server role (the apply " "thread) reached the CLIENT persistent-map producer for buffer lifetime %llu " "[%zu, +%zu). Under an active transport the server's staged copy is the draw's " @@ -474,10 +476,10 @@ namespace MobileGL::MG_State::GLState { } void BufferObject::NotePersistentMapStateChanged() { - if (!MG_Remote::Client::PersistentMapTracker::PushIsArmed()) return; - MG_Remote::Client::PersistentMapTracker::Instance().NoteMapStateChanged(*this); + if (!MG_Record::PersistentMapTracker::PushIsArmed()) return; + MG_Record::PersistentMapTracker::Instance().NoteMapStateChanged(*this); // Teardown must retire membership, but only the client publishes live-host-write state. - if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) return; + if (MG_Record::PersistentMapTracker::OnServerRole()) return; // THE LIVE-HOST-WRITES BIT (ARCHITECTURE.md 12, CONTRACT-P5.md section 3). A live // WRITE map - persistent or not - mutates the shadow with no call, no serial and no @@ -511,7 +513,7 @@ namespace MobileGL::MG_State::GLState { // carries are real, current and the cheapest honest ones there are. A zero-length // record would have been the alternative and it is illegal by contract rule A. if (m_size == 0) return; - const Uint64 blockBytes = MG_Remote::Client::PersistentMapTracker::BlockBytes(); + const Uint64 blockBytes = MG_Record::PersistentMapTracker::BlockBytes(); // MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 TURNS THE WHOLE MECHANISM OFF, STATE RECORD // INCLUDED (E3(a)). An earlier cut skipped the blocks and still shipped a whole buffer // here, which meant an unmap delivered the bytes the control exists to withhold - and @@ -930,7 +932,7 @@ namespace MobileGL::MG_State::GLState { // P11 B2 made this reachable (a T0 adoption), so the tracker lets go of the // shadow range WHILE THE SHADOW IS STILL OURS: AdoptPersistentMap frees it, and // an untrack after that would mprotect pages the buffer no longer owns. - MG_Remote::Client::PersistentMapTracker::Instance().Forget(*this); + MG_Record::PersistentMapTracker::Instance().Forget(*this); #endif m_resource.AdoptPersistentMap(pushedBase); #if MOBILEGL_BUILD_RECORD_ARM diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.h b/MobileGL/MG_State/GLState/BufferState/BufferObject.h index 47d89726..7de8955d 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.h @@ -200,7 +200,7 @@ namespace MobileGL { // the private NotifySubData - the same serial, the same defined-content flag, the // same record - so the split arm and the monolith arm differ in HOW the span is // cut and in nothing else. Called only by - // MG_Remote::Client::PersistentMapTracker, which owns the cutting. + // MG_Record::PersistentMapTracker, which owns the cutting. void PushMappedSpanBlock(SizeT offset, SizeT size); // Called on every event that can move the tracker's membership predicate: map, diff --git a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp similarity index 96% rename from MobileGL/MG_Remote/Client/PersistentMapTracker.cpp rename to MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp index 22d8d4df..306fa03f 100644 --- a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp +++ b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp @@ -1,4 +1,4 @@ -// MobileGL - MobileGL/MG_Remote/Client/PersistentMapTracker.cpp +// MobileGL - MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt @@ -7,9 +7,8 @@ // End of Source File Header #include "PersistentMapTracker.h" -#include "ClientSession.h" -#include -#include +#include +#include #include #include @@ -17,7 +16,7 @@ #include #include #include -#include +#include #include @@ -30,7 +29,7 @@ #include #include -namespace MobileGL::MG_Remote::Client { +namespace MobileGL::MG_Record { using MG_State::GLState::BufferObject; @@ -714,7 +713,9 @@ namespace MobileGL::MG_Remote::Client { // guard, two acquire loads and an out-of-line thread::id comparison. It stays out of line // here for WireTables.cpp's reason beside RunsAsTheServerRole: inlining it would put a // server header inside PersistentMapTracker.h, which MG_State's BufferObject.cpp includes. - Bool PersistentMapTracker::OnServerRole() { return Server::ServerLoop::OnApplyThread(); } + // P13 W5: the apply-thread question through MG_Pipe's role probe (MG_Remote installs it; a library + // without a transport has no server role, so it is false there). + Bool PersistentMapTracker::OnServerRole() { return MG_Pipe::MGPipeServerArm(); } // SyncPersistentMappedRange's early-out chain (BufferObject.cpp:341-353), in its order, // read as a membership test. Every line here has a line there; if one of them moves, the @@ -757,7 +758,7 @@ namespace MobileGL::MG_Remote::Client { // A buffer mapped on a LOST context (a thread still on one, or a session that is lost) // never becomes a member: the push before every verb would carry its bytes to whichever // session is current, and a fresh one never created the buffer. - if (IsLivePersistentMap(buffer) && !ClientSession::DeviceLost()) { + if (IsLivePersistentMap(buffer) && !MG_Pipe::MGPipeClientDeviceLost()) { const auto range = buffer.GetMappedRange(); // The mprotect arm registers beside the hash arm: tracked buffers are pushed // from the fault bitmap, everything else keeps the content scan. A failed @@ -862,7 +863,7 @@ namespace MobileGL::MG_Remote::Client { void PersistentMapTracker::PushBlocksFor(BufferObject& buffer) { if (!PushIsArmed()) return; if (OnServerRole()) { - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map " + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map " "producer belongs to the client; the server consumes transported bytes"); } PushBlocksForChecked(buffer); @@ -1084,7 +1085,7 @@ namespace MobileGL::MG_Remote::Client { void PersistentMapTracker::PushAllMembers() { if (!PushIsArmed()) return; if (OnServerRole()) { - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map " + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map " "producer belongs to the client; the server consumes transported bytes"); } if (m_livePersistentMaps.empty()) return; @@ -1101,7 +1102,7 @@ namespace MobileGL::MG_Remote::Client { void PersistentMapTracker::PushDrawConsumers() { if (!PushIsArmed()) return; if (OnServerRole()) { - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map " + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map " "producer belongs to the client; the server consumes transported bytes"); } if (m_livePersistentMaps.empty()) return; @@ -1258,38 +1259,5 @@ namespace MobileGL::MG_Remote::Client { PersistentMapTracker::Instance().PushAllMembers(); } - Bool AdoptTierIsEmulate() { - const Uint32 tier = MG_Config::Ipc.AdoptTier; - if (tier == 2) return true; - // P11 B2: UNSET (T0 by default) reaching the T2 path - settled or not - is T2, quietly: a - // session-free caller (a unit case, a harness) runs the P5 default it always ran, and a - // settled session already counted its fallback at the handshake or its first map_persistent. - if (tier == MG_Config::kAdoptTierUnset) return true; - // P11 A1 (CONTRACT-P11 §1): THE VERDICT IS THE HANDSHAKE'S, NOT THIS CALL'S. Both roles - // settle the tier from the knob and the data plane before the first record - // (Transport/AdoptTier.cpp): a stream refuses T0/T1 by name and runs T2, a client over - // shared segments dies by name there, and a server over shared segments serves the T2 - // its client settled. So a settled session reaching this with 0/1 is a T2 session and - // says nothing more - the one line was the handshake's. - if (tier <= 1 && Transport::AdoptTierSettledAtHandshake()) return true; - // A map_persistent NO handshake settled (a session-free caller) is still a NAMED - // refusal rather than a silent T2: falling back would make `MOBILEGL_IPC_ADOPT_TIER=0` - // look like a working T0 run and silently produce pmap bytes it must not produce. - // Anything above 2 is not a tier at all. - // `dl` (CONTRACT-P6 5.2): the family word every death here carries. - if (tier <= 1) { - SessionFail(MGFatalFamily::UnimplementedAdoptTier, - "MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u " - "reached a map_persistent that no handshake settled; T0 and T1 are not " - "implemented and only T2 (emulate) runs.", - static_cast(tier), static_cast(tier)); - } else { - SessionFail(MGFatalFamily::UnimplementedAdoptTier, - "MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is " - "not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the " - "P5 default).", - static_cast(tier), static_cast(tier)); - } - } -} // namespace MobileGL::MG_Remote::Client +} // namespace MobileGL::MG_Record diff --git a/MobileGL/MG_Remote/Client/PersistentMapTracker.h b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h similarity index 93% rename from MobileGL/MG_Remote/Client/PersistentMapTracker.h rename to MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h index 75b6850d..a2c8a347 100644 --- a/MobileGL/MG_Remote/Client/PersistentMapTracker.h +++ b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h @@ -1,4 +1,4 @@ -// MobileGL - MobileGL/MG_Remote/Client/PersistentMapTracker.h +// MobileGL - MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt @@ -61,7 +61,7 @@ namespace MobileGL::MG_State::GLState { class BufferObject; } -namespace MobileGL::MG_Remote::Client { +namespace MobileGL::MG_Record { // The mprotect tracker's per-map slot, declared at namespace scope rather than in the // .cpp's anonymous namespace so PersistentMapTracker's membership map can CACHE a @@ -270,18 +270,5 @@ namespace MobileGL::MG_Remote::Client { // draw-path binding walks move to the client. void PushPersistentMapsBeforeVerb(); - // R-6's tier gate, read at use. True for MOBILEGL_IPC_ADOPT_TIER=2, the only implemented - // tier. The tiers are design/07's: T0 = the client allocates an AHardwareBuffer and the - // server imports it (P11 package B), T1 = the server exports an opaque fd (closed), T2 = - // decline and push. 0 and 1 are SETTLED AT THE HANDSHAKE (P11 A1, Transport/AdoptTier.h): - // a stream refuses them by name and runs T2, a client over shared segments dies by name - // there. So a settled session answers true here; only a map_persistent no handshake - // settled is still a named refusal. It is asked - // by MGPipeApplyMapPersistent, which is where the decline is decided, so the client's - // three adoption call sites keep their existing "null means declined" branch and the - // map-persistent-roundtrips counter keeps counting ATTEMPTS in both arms (E3(c) asserts - // mpr is equal between the monolith and the split arm, which is only true if the decline - // happens after the count, on the applier's side of the emission). - Bool AdoptTierIsEmulate(); -} // namespace MobileGL::MG_Remote::Client +} // namespace MobileGL::MG_Record diff --git a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp index 024744ce..8d82c48f 100644 --- a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp @@ -27,7 +27,8 @@ #if MOBILEGL_BUILD_DISAGGREGATED #include -#include +#include +#include #include #endif @@ -38,7 +39,7 @@ namespace { #if MOBILEGL_BUILD_DISAGGREGATED using MG_State::GLState::BufferObject; using MG_Remote::Client::GpuWriteProducer; - using MG_Remote::Client::PersistentMapTracker; + using MG_Record::PersistentMapTracker; // A backend that MINTS a persistent mapping, for the one case that needs the adopted arm // (TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo). Only AcquirePersistentMap is @@ -390,7 +391,7 @@ TEST_F(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { // ...and the consequence, which is the one that would actually corrupt something. const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush); - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush) - before, static_cast(kSize)) << "the declined twin's 4096 bytes and nothing else: the adopted buffer must contribute no " @@ -414,7 +415,7 @@ TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*buffer)); const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush); - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 4u) << "a 256 KiB span at a 64 KiB block size is four records, not one"; @@ -433,7 +434,7 @@ TEST_F(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { auto buffer = MakeBuffer(22u, kSize); buffer->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 2u); EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast(kSize)); buffer->ReleaseMemory(false); @@ -482,7 +483,7 @@ TEST_F(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) << "the rising-edge state record went out with the push disabled"; const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush); - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 0u); EXPECT_EQ(MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush), before) << "MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 must ship nothing, so that " @@ -621,10 +622,10 @@ TEST_F(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) { // Fresh state ships everything once, then re-arms; with nothing written a second push // ships nothing (and, with no edges, hashes nothing). - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 1u); EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast(kSize)); - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 1u); // The one page IS protected: a write through the application's pointer faults into the @@ -634,7 +635,7 @@ TEST_F(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) { mapped[kSize - 1] = 0x5A; EXPECT_GT(PersistentMapTracker::FaultEpochForTest(), epochBefore) << "the sub-page map's page was not protected: the write did not fault"; - MG_Remote::Client::PushPersistentMapsBeforeVerb(); + MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 2u); EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast(2 * kSize)) << "the push is clamped to [begin, end) even though the protected page is wider"; diff --git a/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp b/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp index fad7d2de..7c3e2acc 100644 --- a/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp +++ b/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp @@ -45,7 +45,7 @@ // Neither revert can be hidden by the other, which is the property R-16 asks for. #include -#include +#include #include #include @@ -68,7 +68,7 @@ namespace { using MobileGL::SizeT; using MobileGL::Uint64; using MobileGL::Uint8; - using MobileGL::MG_Remote::Client::PersistentMapTracker; + using MobileGL::MG_Record::PersistentMapTracker; using MobileGL::MG_State::GLState::MapAlignedData; using MobileGL::MG_State::GLState::ShadowAllocationBytesFor; diff --git a/MobileGL/MG_Test/Wire/RemoteClientControls.inc b/MobileGL/MG_Test/Wire/RemoteClientControls.inc index 48c1ea58..f4257e2f 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientControls.inc +++ b/MobileGL/MG_Test/Wire/RemoteClientControls.inc @@ -638,7 +638,7 @@ TEST(RemoteClientControls, PersistentMapProducerRefusesTheApplyThread) { const auto child = RunInChild([] { StartControlSession(); Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void*) { - PersistentMapTracker::Instance().PushAllMembers(); + MG_Record::PersistentMapTracker::Instance().PushAllMembers(); return MOBILEGL_OK; }, nullptr); }); diff --git a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp index ec355cd0..5fd0f889 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp +++ b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp @@ -45,7 +45,7 @@ #include #include #include -#include +#include #include #include #include