[Refactor] (P13 W5 S5): the record arm's three knobs move from IpcTable to RecordArmTable, which every record-arm build parses.

This commit is contained in:
2026-10-06 12:01:49 -04:00
parent b050c60b32
commit b13af729e4
7 changed files with 67 additions and 45 deletions
+31 -22
View File
@@ -462,6 +462,37 @@ namespace MobileGL::MG_Config {
NamedPipe = 4, // P6: Windows named pipe (Endpoint = <name>)
};
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W5: THE RECORD ARM'S OWN KNOBS. They kept their MOBILEGL_IPC_* names (logs, CI lanes and
// notes spell them so), but they tune the record arm - the persistent-map push and Magma's wire
// stores - which runs in a library without a transport too, so they are not IpcTable's.
struct RecordArmTable {
// MOBILEGL_IPC_WIRE_DEFERRED_MB (P7 wave 4 M2, ID-P7-32): the SERVER's budget, in MiB,
// for orphaned wire buffer stores - the old VkBuffer every glBufferData that crosses the
// wire leaves behind - that a GPU command recorded but not yet retired may still name.
// Stores no command names are destroyed at once and stores whose last submission has
// retired are destroyed at the next park; this bounds the REST. When the parked bytes
// exceed it after a sweep, the server flushes what it has recorded and waits for it
// (WaitForWireBufferHostAccess's sync point, mid-frame), which retires every one. It is
// not a frame count because a frame is not bounded: a snapshot-exiting pbuffer replay
// delivers one present for 1.3 M calls. The same sync point also fires above a fixed
// 1024 parked stores (VkBufferManager::kWireDeferredCountCeiling), because small
// orphans never reach a byte budget. 0 IS THE NEGATIVE CONTROL for both, not "unlimited
// by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound
// and MagmaWireReclaimScenario's watermark cases must go red.
Uint32 WireDeferredMb = 64;
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
// 0 IS A NEGATIVE CONTROL, NOT "unlimited": it disables the push, and
// PersistentCoherentMapScenario must go RED under it (exit gate E3(a)).
Uint32 PersistentBlockKb = 64;
// MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS: 1 = the persistent-map push ships only
// blocks whose xxHash64 changed since the last push, instead of the whole mapped
// range every verb. 0 restores the whole-range push (A/B control).
Uint32 PersistentHashSuppress = 1;
};
extern RecordArmTable RecordArm;
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// Parsed once by MG_ConfigLoader::Init(). Defaults to Monolith even here: building the
// transport in is not the same as using it, and every existing lane of a build-split
@@ -520,20 +551,6 @@ namespace MobileGL::MG_Config {
// stage chunk budget (MGPipeStageChunkBytes, a quarter of this), and a record type with
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
Uint32 StageMb = 32;
// MOBILEGL_IPC_WIRE_DEFERRED_MB (P7 wave 4 M2, ID-P7-32): the SERVER's budget, in MiB,
// for orphaned wire buffer stores - the old VkBuffer every glBufferData that crosses the
// wire leaves behind - that a GPU command recorded but not yet retired may still name.
// Stores no command names are destroyed at once and stores whose last submission has
// retired are destroyed at the next park; this bounds the REST. When the parked bytes
// exceed it after a sweep, the server flushes what it has recorded and waits for it
// (WaitForWireBufferHostAccess's sync point, mid-frame), which retires every one. It is
// not a frame count because a frame is not bounded: a snapshot-exiting pbuffer replay
// delivers one present for 1.3 M calls. The same sync point also fires above a fixed
// 1024 parked stores (VkBufferManager::kWireDeferredCountCeiling), because small
// orphans never reach a byte budget. 0 IS THE NEGATIVE CONTROL for both, not "unlimited
// by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound
// and MagmaWireReclaimScenario's watermark cases must go red.
Uint32 WireDeferredMb = 64;
// MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction.
Uint32 SpinUs = 50;
// MOBILEGL_IPC_EVENT_WAIT_MS (PH-6, ID-P7-2): the SERVER's patience, in ms, for ONE
@@ -551,14 +568,6 @@ namespace MobileGL::MG_Config {
// spent twice. Read by the server only (ServerSpawn.cpp passes it to a spawned child);
// the lockstep arm (no kCapRunAheadApply, i.e. Magma) never waits and keeps P5C's Fatal.
Uint32 EventWaitMs = 2000;
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
// 0 IS A NEGATIVE CONTROL, NOT "unlimited": it disables the push, and
// PersistentCoherentMapScenario must go RED under it (exit gate E3(a)).
Uint32 PersistentBlockKb = 64;
// MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS: 1 = the persistent-map push ships only
// blocks whose xxHash64 changed since the last push, instead of the whole mapped
// range every verb. 0 restores the whole-range push (A/B control).
Uint32 PersistentHashSuppress = 1;
// MOBILEGL_IPC_BATCH_WAITS: 1 = value-class records (kCtxState / kCtxCso / kCtxObject
// with no reply slot) are published without waiting for their own apply; the barrier
// is taken at the next pull-reading verb (kCtxVerb syncs, queries, screen rows, and
+24 -11
View File
@@ -40,6 +40,9 @@ namespace MobileGL::MG_Config {
IpcTable Ipc;
Bool MonolithTakesRecordArm = false;
#endif
#if MOBILEGL_BUILD_RECORD_ARM
RecordArmTable RecordArm;
#endif
} // namespace MobileGL::MG_Config
namespace MobileGL::MG_ConfigLoader {
@@ -562,20 +565,11 @@ namespace MobileGL::MG_ConfigLoader {
// are sanity, not policy.
ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024);
ipc.StageMb = QueryEnvUint32("MOBILEGL_IPC_STAGE_MB", 32, 1, 4096);
// P7 wave 4 M2 (Config.h has the semantics). 0 is admitted ON PURPOSE as the negative
// control: no forced sync, and MagmaWireReclaimScenario's watermark case must go red.
ipc.WireDeferredMb = QueryEnvUint32("MOBILEGL_IPC_WIRE_DEFERRED_MB", 64, 0, 65536);
ipc.SpinUs = QueryEnvUint32("MOBILEGL_IPC_SPIN_US", 50, 0, 1000000);
// PH-6 (ID-P7-2; Config.h has the semantics). 0 is NOT admitted: a server with no
// patience at all would forfeit a healthy run-ahead client the first time its ring
// filled between two of the client's drains, which is an ordinary backlog.
ipc.EventWaitMs = QueryEnvUint32("MOBILEGL_IPC_EVENT_WAIT_MS", 2000, 1, 600000);
// 0 is admitted ON PURPOSE and is the negative control of exit gate E3(a): it turns
// the persistent-map push OFF, and PersistentCoherentMapScenario must go red.
ipc.PersistentBlockKb = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_BLOCK_KB", 64, 0, 65536);
// Whole-range push is the 0 arm; with it on (default) only blocks whose
// xxHash64 changed since the last push are shipped.
ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1);
ipc.BatchWaits = QueryEnvUint32("MOBILEGL_IPC_BATCH_WAITS", 1, 0, 1);
// The create window. DEFAULT 6, and it works because a link that declares RetainsReplies
// keeps a declared answer until its reader takes it - Config.h carries the three reasons it
@@ -699,8 +693,8 @@ namespace MobileGL::MG_ConfigLoader {
"persistent-block=%uKiB create-window=%u pbo-readback-sync=%u poll-escalate=%u "
"adopt-tier=%s allow-t0=%u verb-barrier=%u run-ahead=%u present-credit=%u control-timeout=%ums "
"cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s",
ipc.RingMb, ipc.StageMb, ipc.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs,
ipc.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate,
ipc.RingMb, ipc.StageMb, MG_Config::RecordArm.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs,
MG_Config::RecordArm.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate,
ipc.AdoptTier == 0 ? "0" : ipc.AdoptTier == 1 ? "1" : ipc.AdoptTier == 2 ? "2" : "unset(T0)",
ipc.AllowAdoptT0,
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.ColdStartMs,
@@ -712,6 +706,22 @@ namespace MobileGL::MG_ConfigLoader {
"expected to fail: the client still pulls 31 of 63 PipeInputs fields from a "
"live GLContext, so an unbarriered queue lets the server read future values");
}
}
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// P13 W5: the record arm's knobs (Config.h RecordArmTable), in every build that has the arm.
inline void InitRecordArm() {
MG_Config::RecordArmTable& ipc = MG_Config::RecordArm;
// P7 wave 4 M2 (Config.h has the semantics). 0 is admitted ON PURPOSE as the negative
// control: no forced sync, and MagmaWireReclaimScenario's watermark case must go red.
ipc.WireDeferredMb = QueryEnvUint32("MOBILEGL_IPC_WIRE_DEFERRED_MB", 64, 0, 65536);
// 0 is admitted ON PURPOSE and is the negative control of exit gate E3(a): it turns
// the persistent-map push OFF, and PersistentCoherentMapScenario must go red.
ipc.PersistentBlockKb = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_BLOCK_KB", 64, 0, 65536);
// Whole-range push is the 0 arm; with it on (default) only blocks whose
// xxHash64 changed since the last push are shipped.
ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1);
if (ipc.WireDeferredMb == 0) {
MGLOG_W("Config: MOBILEGL_IPC_WIRE_DEFERRED_MB=0 is the M2 NEGATIVE CONTROL: the "
"server never forces a sync for orphaned wire buffer stores, so a long frame "
@@ -730,6 +740,9 @@ namespace MobileGL::MG_ConfigLoader {
InitBackendType();
InitFeatures();
#if MOBILEGL_BUILD_RECORD_ARM
InitRecordArm();
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// After InitFeatures, so the one line InitIpc logs is the last word on this run's
// configuration, and before the accepted-env map is destroyed just below.
@@ -1111,7 +1111,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void VkBufferManager::EnforceWireDeferredWatermark() {
const Uint64 budget = static_cast<Uint64>(MG_Config::Ipc.WireDeferredMb) * 1024u * 1024u;
const Uint64 budget = static_cast<Uint64>(MG_Config::RecordArm.WireDeferredMb) * 1024u * 1024u;
// TWO TRIGGERS, ONE SWITCH. Bytes are what the knob names, but a VkBuffer costs per
// OBJECT as well as per byte, and small orphans never reach a byte budget: measured on
// bsl-esc-menu-854 (spawn, lavapipe), one stretch with no submission parked 12,498 stores
@@ -232,7 +232,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// back, and none was persistently mapped, in between - so a memo of resolved
// slices needs no per-buffer re-check. See AcquirePersistentMap for the mapping half.
Uint64 GetSliceEpochCounter() const { return m_sliceEpochCounter; }
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
// Bumped every time this manager destroys a WIRE store's VkBuffer (see
// m_wireStoreDestroyEpoch). Unchanged since a memo was taken means no VkBuffer handle
// that memo names can have been freed and re-minted in between, which is the one
@@ -408,7 +408,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<Vector<VkBufferObject>> m_deferredBufferReleases;
Vector<Vector<SharedPtr<VkBufferResource>>> m_deferredResourceReleases;
Vector<WeakPtr<VkBufferResource>> m_liveResources;
#if MOBILEGL_BUILD_DISAGGREGATED
#if MOBILEGL_BUILD_RECORD_ARM
std::unordered_map<Uint64, WireBufferResource> m_wireBuffers;
// See DeferredWireRelease. ONE FLAT LIST rather than the per-frame-slot buckets above:
// the whole point is that these entries do not wait for a frame slot to come round.
@@ -700,7 +700,7 @@ namespace MobileGL::MG_Remote::Client {
}
Uint64 PersistentMapTracker::BlockBytes() {
return static_cast<Uint64>(MG_Config::Ipc.PersistentBlockKb) * 1024ull;
return static_cast<Uint64>(MG_Config::RecordArm.PersistentBlockKb) * 1024ull;
}
Bool PersistentMapTracker::PushIsArmed() {
@@ -1049,7 +1049,7 @@ namespace MobileGL::MG_Remote::Client {
// in both directions.
BlockHashState* state = nullptr;
const Uint8* shadow = nullptr;
if (MG_Config::Ipc.PersistentHashSuppress != 0) {
if (MG_Config::RecordArm.PersistentHashSuppress != 0) {
shadow = buffer.MappedData();
if (shadow != nullptr) {
state = &m_blockHashes[buffer.GetLifetimeId()];
+4 -4
View File
@@ -64,13 +64,13 @@ namespace {
protected:
void SetUp() override {
m_transport = MG_Config::Transport;
m_blockKb = MG_Config::Ipc.PersistentBlockKb;
m_blockKb = MG_Config::RecordArm.PersistentBlockKb;
m_adoptTier = MG_Config::Ipc.AdoptTier;
m_pipeStats = MG_Config::Features.PipeStats;
m_context = Move(MG_State::pGLContext);
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
MG_Config::Transport = MG_Config::TransportMode::InProcess;
MG_Config::Ipc.PersistentBlockKb = 64;
MG_Config::RecordArm.PersistentBlockKb = 64;
MG_Config::Ipc.AdoptTier = 2;
MG_Config::Features.PipeStats = true;
MG_Util::PipeStats::Init();
@@ -87,7 +87,7 @@ namespace {
PersistentMapTracker::Instance().ClearForTest();
MG_State::pGLContext = Move(m_context);
MG_Config::Transport = m_transport;
MG_Config::Ipc.PersistentBlockKb = m_blockKb;
MG_Config::RecordArm.PersistentBlockKb = m_blockKb;
MG_Config::Ipc.AdoptTier = m_adoptTier;
MG_Config::Features.PipeStats = m_pipeStats;
MG_Util::PipeStats::Init();
@@ -470,7 +470,7 @@ TEST_F(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) {
// whole buffer at unmap, so a scenario that unmapped before reading back went green and the
// control was dead.
TEST_F(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) {
MG_Config::Ipc.PersistentBlockKb = 0;
MG_Config::RecordArm.PersistentBlockKb = 0;
constexpr SizeT kSize = 128u * 1024u;
auto buffer = MakeBuffer(23u, kSize);
+3 -3
View File
@@ -4592,12 +4592,12 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue
// case reaches is optional.
const Uint64 previousPush = MG_Config::Features.PipePush;
const auto previousTransport = MG_Config::Transport;
const Uint32 previousBlockKb = MG_Config::Ipc.PersistentBlockKb;
const Uint32 previousBlockKb = MG_Config::RecordArm.PersistentBlockKb;
MG_Pipe::MGPipeResourceOps ops{};
MG_Config::Features.PipePush |= MG_Pipe::kMGPipeSubsystemResources;
MG_Pipe::MGPipeSetResourceOps(&ops);
MG_Config::Transport = MG_Config::TransportMode::InProcess;
MG_Config::Ipc.PersistentBlockKb = 64;
MG_Config::RecordArm.PersistentBlockKb = 64;
// P5 c1 / R-8: UNDER SPLIT THE OP TABLE IS NO LONGER THE ARMING CONDITION, and this case is
// the first place that shows. `MGPipeSetResourceOps(&ops)` is the SERVER's registration; the
// client's liveness gate now reads the caps mirror's consumer mask instead, because under a
@@ -4691,7 +4691,7 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue
BufferImpl::g_backendBufferResources.ReleaseByHandle(res);
}
MG_Config::Ipc.PersistentBlockKb = previousBlockKb;
MG_Config::RecordArm.PersistentBlockKb = previousBlockKb;
MG_Config::Transport = previousTransport;
MG_Pipe::MGPipeSetResourceOps(nullptr);
MG_Config::Features.PipePush = previousPush;