mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Pipe, MG_Impl, MG_Remote, MG_Backend] (Disaggregated): P5e id - the barriered seam, the allocator guard and the two scopes
- CONTRACT-P5E §2.1's predicate gets its two names in MG_Pipe/PipeApply.h: MGPipeBarriered(op, payload, st) and MGPipeApplierCurrentRecordIsBarriered(), with MGPipeApplierState::CurrentRecordBarriered as the per-record flag that PipeApplier::ApplyOne stamps before every decode. The body answers TRUE for every record until ra lands the wait rule, so lockstep is unchanged and this package alters no behaviour; ra rebases onto that line rather than adding it. Package c0e owns the final declaration of both names - the overlap is one hunk. - §4.4: MGPipeRefuseAllocatorFromApplyThread now exempts a probe only inside a named scope AND while the record being applied is barriered. The client's wait is the whole of what makes an apply-thread frontend probe safe; behind an unbarriered record the allocator's free list and lifetimeId -> slot map are moving under the read, so the value is wrong by construction - there is no "count it" arm and MOBILEGL_IPC_STRICT_ERRORS is not consulted. - MGPipeApplierIsUnbarrieredApply() and MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply() are the non-allocator half of the same rule, for the surfaces that answer from a frontend SharedPtr without calling the allocator (SlotTables.h's state note and its reader). - Ruling 12: MGPipeReverseAnnouncementScope -> MagmaP7AllocatorDebtScope, keyed on MG_Config::ActiveBackendType == DirectVulkan. Magma's four apply-thread allocator debts move onto it (VulkanRenderer's two hidden-resource shutdowns and the named-blit endpoint resolve, ResourceTracker's GPU-write announcement); an Espryt probe can no longer borrow Magma's exemption by constructing its scope. - BufferImpl::HandleOfBuffer is Espryt's debt, not Magma's, so it moves to MGPipeFrontendKeyedRegistryScope - which the Magma-keyed scope would not have exempted at all. It retires when vi/sb carry the handle into EnsureBufferResourceForHandle / IsBufferDrawCleanByHandle.
This commit is contained in:
@@ -2932,10 +2932,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c merge coordination (CONTRACT-P5C §3.1's named exemption): every caller of
|
||||
// this function is a site whose handle-carrying records (set_shader_buffers /
|
||||
// set_stream_output_targets) the client does not emit until P4b, so the probe
|
||||
// set_stream_output_targets) the client does not emit until sb, so the probe
|
||||
// below runs inside the scoped exemption. The scope, not a silent guard removal,
|
||||
// is what keeps the debt named and greppable.
|
||||
const MG_Pipe::MGPipeReverseAnnouncementScope reverseAnnouncement;
|
||||
//
|
||||
// P5e (id), ruling 12: THE SCOPE CHANGED UNDER THIS SITE AND THE SITE HAD TO MOVE.
|
||||
// The exemption it used to name is now MagmaP7AllocatorDebtScope and is keyed on a
|
||||
// DirectVulkan server (Magma's four P7 debts); this is Espryt's, and it belongs to
|
||||
// the frontend-keyed registry family with the rest of the Espryt debt. Which scope
|
||||
// it names is not cosmetic: the Magma one would not exempt it here at all. It
|
||||
// retires when vi/sb carry the handle into EnsureBufferResourceForHandle /
|
||||
// IsBufferDrawCleanByHandle / MarkBufferGpuWrittenByHandle, which is the last
|
||||
// caller set this function has.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
#endif
|
||||
return g_backendBufferResources.HandleOf(bufferObject);
|
||||
}
|
||||
|
||||
@@ -4551,7 +4551,12 @@ void main() {
|
||||
// delete, so the scope admits the probe as named debt rather than letting the guard
|
||||
// Fatal at server teardown. P7 gives these resources storage that is not a frontend
|
||||
// object.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
//
|
||||
// P5e (id), ruling 12: one of Magma's FOUR apply-thread allocator debts, and the scope
|
||||
// is now named after that debt rather than after the frontend-keyed registry - Espryt's
|
||||
// half of which P5e is retiring, while this one waits for P7. The exemption is keyed on
|
||||
// a DirectVulkan server, which is what this file always is.
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaP7AllocatorDebt;
|
||||
#endif
|
||||
m_blitResources = {};
|
||||
}
|
||||
@@ -4630,8 +4635,9 @@ void main() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Same shape as ShutdownBlitResources above: the hidden depth-mipmap program is a
|
||||
// frontend object created and destroyed by the server backend on the apply thread, and
|
||||
// its destructor's lifetime-id probe is admitted here as named debt.
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
// its destructor's lifetime-id probe is admitted here as named debt - Magma's, P7's to
|
||||
// retire, which is what the scope's P5e name says (ruling 12).
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaP7AllocatorDebt;
|
||||
#endif
|
||||
m_depthMipmapResources = {};
|
||||
}
|
||||
@@ -8974,8 +8980,11 @@ void main() {
|
||||
// the resolution here is frontend-keyed - the handle was minted over the frontend
|
||||
// object's lifetime id, and the two probes below (the client allocator's slot entry
|
||||
// and the frontend context's framebuffer pool) are named debt inside the scope, the
|
||||
// same shape as Espryt's StateForHandle arm: P3b/P4b retire it by carrying the object
|
||||
// identity in the record.
|
||||
// same shape as Espryt's StateForHandle arm: P7 retires it by carrying the object
|
||||
// identity in the record. P5e (id), ruling 12: the third of Magma's four debts, so the
|
||||
// scope it rides in is MagmaP7AllocatorDebtScope. Magma stays lockstep for the whole of
|
||||
// P5e (CONTRACT-P5E §6.1), so the record being applied here is always barriered and the
|
||||
// exemption still holds.
|
||||
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
||||
auto& applierState = MG_Pipe::MGPipeApplier();
|
||||
const MG_Pipe::MGPipeHandle readHandle = applierState.VerbBlitReadFbo;
|
||||
@@ -8983,7 +8992,7 @@ void main() {
|
||||
if (!MG_Pipe::MGPipeHandleIsNull(readHandle) || !MG_Pipe::MGPipeHandleIsNull(drawHandle)) {
|
||||
const auto resolveEndpoint = [](MG_Pipe::MGPipeHandle handle)
|
||||
-> SharedPtr<MG_State::GLState::FramebufferObject> {
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope frontendKeyedRegistry;
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaP7AllocatorDebt;
|
||||
if (handle == MG_Pipe::kMGPipeDefaultFramebuffer) {
|
||||
return MG_State::pGLContext ? MG_State::pGLContext->GetFramebufferObject(0) : nullptr;
|
||||
}
|
||||
|
||||
@@ -717,10 +717,15 @@ namespace MobileGL::MG_Pipe {
|
||||
const MGPipeHandle res = [&]() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// CONTRACT-P5C §3.1's named exemption, Magma's half: the binding records that
|
||||
// would carry this buffer's handle are P4b's to emit and Magma's server-side
|
||||
// would carry this buffer's handle are sb's to emit and Magma's server-side
|
||||
// binding table is P7's, so until then the probe runs inside the scope - the
|
||||
// debt's named, greppable form rather than a silent guard removal.
|
||||
const MGPipeReverseAnnouncementScope reverseAnnouncement;
|
||||
//
|
||||
// P5e (id), ruling 12: the FOURTH of Magma's apply-thread allocator debts and the
|
||||
// last user of the scope P5e renamed. Espryt never reaches this arm under a
|
||||
// transport - MarkBufferGpuWrittenByHandle carries the handle from the record side
|
||||
// (sb/fb) - so the DirectVulkan key on the exemption costs it nothing.
|
||||
const MagmaP7AllocatorDebtScope magmaP7AllocatorDebt;
|
||||
#endif
|
||||
return MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, bufferObject->GetLifetimeId());
|
||||
}();
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
#include <Config.h>
|
||||
// P5e (id): the guard's exemption is keyed on whether the record being applied is BARRIERED
|
||||
// (CONTRACT-P5E §4.4), and MGPipeApplierCurrentRecordIsBarriered() is where ApplyOne stamps
|
||||
// that. Declarations only - this file names no applier state.
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
@@ -19,20 +23,64 @@
|
||||
|
||||
namespace MobileGL::MG_Pipe {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
Bool MGPipeApplierIsUnbarrieredApply() {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return false;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) return false;
|
||||
return !MGPipeApplierCurrentRecordIsBarriered();
|
||||
}
|
||||
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) return;
|
||||
// The named exemption of CONTRACT-P5C §3.1: the sites whose handle-carrying records
|
||||
// are not emitted yet (P4b/P7) probe read-only inside the scope. The G6
|
||||
// frontend-keyed registry family (P3b/P4b) does the same inside its own scope.
|
||||
if (MGPipeReverseAnnouncementScope::ActiveOnApplyThread()) return;
|
||||
if (MGPipeFrontendKeyedRegistryScope::ActiveOnApplyThread()) return;
|
||||
// CONTRACT-P5E §4.4 AMENDS CONTRACT-P5C §3.1 HERE, AND IT IS THE WHOLE OF WHAT id
|
||||
// CHANGES ABOUT THIS FUNCTION: a named exemption is a debt the CLIENT'S WAIT pays for.
|
||||
// Behind a barriered record the client is parked in WaitForApplied and its allocator
|
||||
// is not moving, so a read-only probe is stale-free; behind an unbarriered one it is
|
||||
// running ahead, and the same probe reads a free list and a lifetimeId -> slot map the
|
||||
// client is concurrently mutating. So the two scopes below exempt nothing at all once
|
||||
// the record is unbarriered - regardless of MOBILEGL_IPC_STRICT_ERRORS, because the
|
||||
// value would be wrong by construction and there is no "count it" arm for that
|
||||
// (CONTRACT-P5E, rule F).
|
||||
//
|
||||
// Every record is barriered until ra lands the wait rule, so this reads exactly as it
|
||||
// did at 2fde7034 for the whole of this phase.
|
||||
if (MGPipeApplierCurrentRecordIsBarriered()) {
|
||||
// Ruling 12: Magma's four P7 debts, and ONLY on a DirectVulkan server. The key is
|
||||
// on the backend kind rather than on the site because the scope is a class anyone
|
||||
// can construct, and an Espryt probe borrowing Magma's exemption is exactly the
|
||||
// hole P5e is closing.
|
||||
if (MagmaP7AllocatorDebtScope::ActiveOnApplyThread() &&
|
||||
MG_Config::ActiveBackendType == BackendType::DirectVulkan) {
|
||||
return;
|
||||
}
|
||||
// The G6 frontend-keyed registry family: the barriered-row sites the per-family
|
||||
// packages have not carried a handle to yet (CONTRACT-P5E §4.4).
|
||||
if (MGPipeFrontendKeyedRegistryScope::ActiveOnApplyThread()) return;
|
||||
}
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"MGPipeSlots\"} - the apply thread called "
|
||||
"MGPipeSlots().%s. With an active transport the client slot allocator is "
|
||||
"client-only memory (CONTRACT-P5C §3.1, rule E): a handle arrives already "
|
||||
"minted in a record, and a server that resolves or mints one off a frontend "
|
||||
"object's lifetime id is reading memory that will not exist on its side of a "
|
||||
"real split",
|
||||
"client-only memory (CONTRACT-P5C §3.1, rule E; CONTRACT-P5E §4.4): a handle "
|
||||
"arrives already minted in a record, and a server that resolves or mints one "
|
||||
"off a frontend object's lifetime id is reading memory that will not exist on "
|
||||
"its side of a real split. A named exemption scope admits it only while the "
|
||||
"record being applied is BARRIERED (barriered=%d) and, for Magma's P7 debt, "
|
||||
"only on a DirectVulkan server",
|
||||
entry, MGPipeApplierCurrentRecordIsBarriered() ? 1 : 0);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// P5e (id): the non-allocator half of the same rule. SlotTables.h's state note, its reader
|
||||
// and ForEachLive's weak-reference walk read server memory KEYED BY FRONTEND IDENTITY and
|
||||
// hand a frontend SharedPtr back, which is the same violation one step removed - no
|
||||
// allocator call, so the guard above never sees it.
|
||||
void MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply(const char* entry) {
|
||||
if (!MGPipeApplierIsUnbarrieredApply()) return;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"MGPipeSlots\"} - the apply thread reached "
|
||||
"BackendSlotTable::%s while applying an UNBARRIERED record. The frontend-keyed "
|
||||
"half of the twin table is monolith glue (CONTRACT-P5E §4.1, §5.8): it answers "
|
||||
"from a frontend object the server has no wait pinning, so the SharedPtr it "
|
||||
"would hand back may already be the client's next object. Resolve the twin "
|
||||
"from the handle the record carried instead",
|
||||
entry);
|
||||
std::abort();
|
||||
}
|
||||
@@ -54,8 +102,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// WHY IT WAS WORTH DOING. A shared-library thread_local costs an __emutls_get_address
|
||||
// call per access. On the MONOLITH's GL thread that symbol is the TOP entry of the
|
||||
// profile at 7.7%, and these two scopes' ctor/dtor are 32.7% of its samples
|
||||
// (MGPipeFrontendKeyedRegistryScope 18.75 ctor + 13.95 dtor, MGPipeReverseAnnouncement
|
||||
// 8.4); on the split's apply thread emutls is 7.4%. The scope is constructed at ~20
|
||||
// (MGPipeFrontendKeyedRegistryScope 18.75 ctor + 13.95 dtor, the scope P5e renamed
|
||||
// MagmaP7AllocatorDebtScope 8.4); on the split's apply thread emutls is 7.4%. The
|
||||
// measurement predates the rename and the numbers are quoted as measured. The scope is
|
||||
// constructed at ~20
|
||||
// sites in DirectGLES.cpp, several inside StateBackendObjectRegistry::HandleOf, which
|
||||
// is per-draw. The cost on the GL thread is now one inlined predicate per end.
|
||||
//
|
||||
@@ -65,23 +115,27 @@ namespace MobileGL::MG_Pipe {
|
||||
// meaning cannot ask for it by accident: whoever wants "is a scope open on THIS
|
||||
// thread" has to add it, and move the counting back, rather than read a false that
|
||||
// looks like an answer.
|
||||
Uint32 g_reverseAnnouncementScopeDepth = 0;
|
||||
Uint32 g_magmaP7AllocatorDebtScopeDepth = 0;
|
||||
}
|
||||
|
||||
MGPipeReverseAnnouncementScope::MGPipeReverseAnnouncementScope()
|
||||
MagmaP7AllocatorDebtScope::MagmaP7AllocatorDebtScope()
|
||||
// Decided ONCE and remembered: the destructor must undo exactly what the constructor
|
||||
// did, and re-asking the predicate would leak a count across a scope that straddled
|
||||
// the apply thread's exit block.
|
||||
: m_counted(MG_Remote::Server::ServerLoop::OnApplyThread()) {
|
||||
if (m_counted) ++g_reverseAnnouncementScopeDepth;
|
||||
if (m_counted) ++g_magmaP7AllocatorDebtScopeDepth;
|
||||
}
|
||||
|
||||
MGPipeReverseAnnouncementScope::~MGPipeReverseAnnouncementScope() {
|
||||
if (m_counted) --g_reverseAnnouncementScopeDepth;
|
||||
MagmaP7AllocatorDebtScope::~MagmaP7AllocatorDebtScope() {
|
||||
if (m_counted) --g_magmaP7AllocatorDebtScopeDepth;
|
||||
}
|
||||
|
||||
Bool MGPipeReverseAnnouncementScope::ActiveOnApplyThread() {
|
||||
return g_reverseAnnouncementScopeDepth != 0;
|
||||
// The DEPTH is not backend-keyed and deliberately so: the backend kind is read by the
|
||||
// GUARD, once, at the moment it decides. Counting only on DirectVulkan would make the
|
||||
// depth's meaning depend on a global that a test can move between the constructor and the
|
||||
// destructor, which is the m_counted bug one level out.
|
||||
Bool MagmaP7AllocatorDebtScope::ActiveOnApplyThread() {
|
||||
return g_magmaP7AllocatorDebtScopeDepth != 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -173,14 +173,37 @@ namespace MobileGL::MG_Pipe {
|
||||
// pull build's bytes do not move (G1).
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry);
|
||||
|
||||
// The NAMED EXEMPTION to the rule above (CONTRACT-P5C §3.1): the family of sites whose
|
||||
// handle-carrying records the client does not EMIT yet. set_shader_buffers and
|
||||
// set_stream_output_targets exist in the catalogue but are P4b's to emit
|
||||
// (SetHashSuppressor.h says so), so the buffer binding-point ensures and the GPU-written
|
||||
// announcement they feed have no record handle to resolve from today. Inside this scope
|
||||
// ONE read-only lifetime-id probe stays legal; the scope is the debt's measurable,
|
||||
// greppable form, and it retires with P4b's emission (Espryt) and P7's server-side
|
||||
// binding table (Magma). Every other apply-thread allocator access stays Fatal.
|
||||
// ---- P5e (id, CONTRACT-P5E §4.4): THE CONDITION UNDER WHICH A NAMED SCOPE EXEMPTS -----
|
||||
//
|
||||
// "This thread is applying a record the client is NOT parked behind." Both scopes below
|
||||
// exempt a probe only while it is FALSE, because the client's wait is the whole of what
|
||||
// makes a frontend read from the apply thread safe: behind a barriered record the client
|
||||
// is blocked in WaitForApplied and its memory is stable; behind an unbarriered one it is
|
||||
// running ahead and the same read is torn or stale BY CONSTRUCTION, which is why rule F
|
||||
// has no "count it" arm and this answer does not consult MOBILEGL_IPC_STRICT_ERRORS.
|
||||
//
|
||||
// False in a monolith process and on any thread but the apply thread, so a GL-thread
|
||||
// caller is never refused by it.
|
||||
Bool MGPipeApplierIsUnbarrieredApply();
|
||||
|
||||
// The refusal for the frontend-keyed surfaces that do NOT touch the allocator and so are
|
||||
// not covered by MGPipeRefuseAllocatorFromApplyThread: the state note, its reader, and
|
||||
// ForEachLive's weak-reference walk (SlotTables.h). They survive P5e as MONOLITH GLUE
|
||||
// (CONTRACT-P5E §5.8) and this is what keeps that claim honest - reaching one of them from
|
||||
// an unbarriered apply is Fatal{RoleViolation, "MGPipeSlots"} naming the member, not a
|
||||
// silently stale SharedPtr. Same name in the refusal as the allocator guard's on purpose:
|
||||
// it is one surface, "server memory keyed by frontend identity", and a reader chasing the
|
||||
// abort should land on the same rule either way.
|
||||
void MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply(const char* entry);
|
||||
|
||||
// The NAMED EXEMPTION to the rule above (CONTRACT-P5C §3.1, as amended by CONTRACT-P5E
|
||||
// §4.4): the family of sites whose handle-carrying records the client does not EMIT yet.
|
||||
// set_shader_buffers and set_stream_output_targets exist in the catalogue but are P4b/sb's
|
||||
// to emit (SetHashSuppressor.h says so), so the buffer binding-point ensures and the
|
||||
// GPU-written announcement they feed have no record handle to resolve from today. Inside
|
||||
// this scope ONE read-only lifetime-id probe stays legal WHILE THE CURRENT RECORD IS
|
||||
// BARRIERED; the scope is the debt's measurable, greppable form, and it retires with P7's
|
||||
// server-side binding table. Every other apply-thread allocator access stays Fatal.
|
||||
//
|
||||
// THE DEPTH IS COUNTED ONLY ON THE APPLY THREAD (P5d round 3, package D). The counter's
|
||||
// only reader is MGPipeRefuseAllocatorFromApplyThread, which returns before it looks unless
|
||||
@@ -196,12 +219,28 @@ namespace MobileGL::MG_Pipe {
|
||||
// monolith process) both ends are now one inlined predicate and a branch instead of an
|
||||
// emutls call, which is where 32.7% of the monolith GL thread's __emutls_get_address - its
|
||||
// top symbol at 7.7% - was going.
|
||||
class MGPipeReverseAnnouncementScope {
|
||||
//
|
||||
// P5e (id), ruling 12: RENAMED FROM MGPipeReverseAnnouncementScope AND KEYED ON THE
|
||||
// BACKEND KIND. The rename is the finding: what is left inside it after P5e is not "the
|
||||
// reverse announcement" - Espryt's half of that moved to the frontend-keyed scope with the
|
||||
// rest of the registry debt - it is MAGMA'S FOUR APPLY-THREAD ALLOCATOR TOUCHES, which P7
|
||||
// retires (VulkanRenderer.cpp's two hidden-resource shutdowns and its named-blit endpoint
|
||||
// resolve, plus MGPipeAnnounceBufferGpuWritten in ResourceTracker.h). Naming the scope
|
||||
// after the debt rather than after one of its sites is what makes "has P7 landed yet" a
|
||||
// grep; naming it after Magma is what stops a new Espryt site being wrapped in it.
|
||||
//
|
||||
// THE EXEMPTION IS REFUSED WHEN THE SERVER BACKEND IS NOT DirectVulkan, and that is the
|
||||
// point of the key rather than a safety belt. Magma stays lockstep for the whole of P5e
|
||||
// (§6.1: the DirectVulkan arm never publishes kCapRunAheadApply, so every record there is
|
||||
// barriered and every probe inside this scope keeps P5C's semantics). Espryt is what P5e
|
||||
// is retiring the lockstep for, so an Espryt probe must not be able to borrow Magma's
|
||||
// exemption - even by wrapping itself in Magma's scope.
|
||||
class MagmaP7AllocatorDebtScope {
|
||||
public:
|
||||
MGPipeReverseAnnouncementScope();
|
||||
~MGPipeReverseAnnouncementScope();
|
||||
MGPipeReverseAnnouncementScope(const MGPipeReverseAnnouncementScope&) = delete;
|
||||
MGPipeReverseAnnouncementScope& operator=(const MGPipeReverseAnnouncementScope&) = delete;
|
||||
MagmaP7AllocatorDebtScope();
|
||||
~MagmaP7AllocatorDebtScope();
|
||||
MagmaP7AllocatorDebtScope(const MagmaP7AllocatorDebtScope&) = delete;
|
||||
MagmaP7AllocatorDebtScope& operator=(const MagmaP7AllocatorDebtScope&) = delete;
|
||||
static Bool ActiveOnApplyThread();
|
||||
|
||||
private:
|
||||
@@ -212,11 +251,21 @@ namespace MobileGL::MG_Pipe {
|
||||
// registry (audit row G6). The texture / sampler-view / FBO-legacy HandleOf probes are
|
||||
// how the server answers "which twin is this frontend object" while the registry is
|
||||
// keyed by frontend identity - server-PRIVATE state whose rekey onto handles is
|
||||
// P3b/P4b's, not P5c's. A probe inside this scope stays a read-only, barrier-held debt;
|
||||
// wrapping a NEW site in it is the greppable act of naming that debt, and an unwrapped
|
||||
// P5e's per-family packages', not P5c's. A probe inside this scope stays a read-only,
|
||||
// BARRIER-HELD debt - and P5e (id) makes the "barrier-held" half literal rather than
|
||||
// documentary: the exemption now holds only while MGPipeApplierCurrentRecordIsBarriered()
|
||||
// (§4.4), i.e. only while the client is actually parked behind the record being applied.
|
||||
// Wrapping a NEW site in it is the greppable act of naming that debt, and an unwrapped
|
||||
// probe from the apply thread is still Fatal{RoleViolation, "MGPipeSlots"}.
|
||||
//
|
||||
// Apply-thread-only depth, and m_counted, for MGPipeReverseAnnouncementScope's reasons
|
||||
// WHAT THE CLASS IS FOR NOW, since P5e deletes most of its sites: vi/sb/pg/tx2/fb delete
|
||||
// their ~20 draw-path constructions with the probes they wrap, and what survives is the
|
||||
// set of BARRIERED-ROW sites (CONTRACT-P5E §4.4: DirectGLES.cpp's CopyTex / GetTexImage /
|
||||
// mipmap-shape / set_storage_block_binding / detach-walk sites, the two verify arms, and -
|
||||
// until vi/sb carry the handle - BufferImpl::HandleOfBuffer). Those keep P5C's semantics
|
||||
// because their records are barriered and their client IS parked.
|
||||
//
|
||||
// Apply-thread-only depth, and m_counted, for MagmaP7AllocatorDebtScope's reasons
|
||||
// above. This is the scope that pays: it is constructed at ~20 sites in DirectGLES.cpp,
|
||||
// several of them inside StateBackendObjectRegistry::HandleOf on the per-draw path, and
|
||||
// its ctor/dtor alone were 18.75% + 13.95% of the monolith GL thread's emutls samples.
|
||||
|
||||
@@ -1212,6 +1212,30 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint64 MGPipeApplierContextSerial() { return g_applier.ContextSerial; }
|
||||
void MGPipeApplierNoteTextureStateMoved() { ++g_applier.TextureShutterSerial; }
|
||||
|
||||
// P5e (id), CONTRACT-P5E §2.1. THE BODY ra REPLACES, and the reason it is a constant here
|
||||
// rather than absent: every caller of the predicate lands with this package, so a missing
|
||||
// body would be a link error in six worktrees at once, and a body that guessed at the wait
|
||||
// rule would change behaviour in the package whose whole claim is that it changes none.
|
||||
// True for every record IS today's rule - the client blocks after publishing every verb
|
||||
// (ClientSession.cpp's :915-959 lockstep path) - so this is a restatement, not a stub.
|
||||
Bool MGPipeBarriered(MGPWireOp op, const void* payload, const MGPipeApplierState& st) {
|
||||
(void)op;
|
||||
(void)payload;
|
||||
(void)st;
|
||||
return true;
|
||||
}
|
||||
|
||||
// §4.4's input. Outside a disaggregated build there is no ApplyOne to stamp it and no
|
||||
// client that could run ahead, so the answer is the constant the monolith has always
|
||||
// behaved as.
|
||||
Bool MGPipeApplierCurrentRecordIsBarriered() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
return g_applier.CurrentRecordBarriered;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void MGPipeSetResourceOps(const MGPipeResourceOps* ops) { g_resourceOps = ops; }
|
||||
const MGPipeResourceOps* MGPipeGetResourceOps() { return g_resourceOps; }
|
||||
|
||||
|
||||
@@ -744,6 +744,23 @@ namespace MobileGL::MG_Pipe {
|
||||
// dispatch_indirect's command buffer (MGPGridInfo::IndirectBuffer).
|
||||
MGPipeHandle VerbDispatchIndirectBuffer = kMGPipeNullHandle;
|
||||
|
||||
// ---- P5e (id, CONTRACT-P5E §2.1 / §4.4): IS THE RECORD BEING APPLIED BARRIERED? ----
|
||||
//
|
||||
// SERVER-PRIVATE and per-record: PipeApplier::ApplyOne writes it from MGPipeBarriered()
|
||||
// before the decode and nothing else may. It is what §4.4's exemption is keyed on - a
|
||||
// frontend probe inside a named scope is legal only while the CLIENT IS PARKED behind
|
||||
// this record, because the wait is the whole of what makes the client's memory stable
|
||||
// to read. An unbarriered record's apply has no such wait, so the same probe would
|
||||
// read memory the client is concurrently moving, and the scope stops exempting it.
|
||||
//
|
||||
// TRUE IS THE ONLY VALUE THIS PHASE PRODUCES, and that is deliberate: MGPipeBarriered
|
||||
// answers true for every record until ra lands the wait rule, so id changes no
|
||||
// behaviour at all (lockstep is unchanged) while every site that must one day ask the
|
||||
// question already asks it. It is also the value a MONOLITH applier keeps - nothing
|
||||
// writes it there and there is no client to run ahead - which is what keeps Magma and
|
||||
// the push-monolith build on P5C's semantics (§6.1).
|
||||
Bool CurrentRecordBarriered = true;
|
||||
|
||||
// Every verb's writer calls this FIRST and then sets its own fields, so no field ever
|
||||
// outlives the verb that wrote it and a reader can treat non-null as "this verb's".
|
||||
void ClearVerbHandles() {
|
||||
@@ -793,6 +810,38 @@ namespace MobileGL::MG_Pipe {
|
||||
// this, so the bump rule lives in exactly one place.
|
||||
void MGPipeApplierNoteTextureStateMoved();
|
||||
|
||||
// ---- P5e: the two halves of the barriered predicate (CONTRACT-P5E §2.1, §4.4) ---------
|
||||
//
|
||||
// OWNERSHIP NOTE, so the duplicate is not mistaken for a fork: package c0e owns the final
|
||||
// declaration of both names in this header. id declares them here because the identity
|
||||
// guard (§4.4) cannot compile without them and the two packages are implemented in
|
||||
// parallel; the integrator resolves the textual overlap at landing.
|
||||
//
|
||||
// MGPWireOp is declared opaquely rather than by including MGPipe.h: the enum is generated
|
||||
// into generated/PipeWire.inc, which MGPipe.h includes INSIDE this namespace, and this
|
||||
// header sits below it (PipeRoute.h includes both). An opaque enum declaration with the
|
||||
// same fixed underlying type is the whole coupling the predicate needs.
|
||||
enum class MGPWireOp : Uint16;
|
||||
|
||||
// "Does the client block after publishing this record?" - a pure function of wire-visible
|
||||
// data, computed identically by the emit table and by the sink (§2.1):
|
||||
//
|
||||
// MGPipeWaitClassFor(op) != kWaitNone // static column
|
||||
// || (MGPipeCallClassFor(op) == kCtxVerb && st.IsTransformFeedbackActive) // (i) XFB
|
||||
// || (op == DrawVbo && (payload.Flags & kDrawClientArrays)) // (ii)
|
||||
//
|
||||
// UNTIL ra LANDS THE WAIT RULE THE ANSWER IS TRUE FOR EVERY RECORD, and that is what keeps
|
||||
// this package behaviour-free: the client waits exactly where it waits today, so every
|
||||
// apply-thread frontend read that is legal today stays legal today. `op` and `payload` are
|
||||
// therefore unread by the body id ships; they are in the signature because ra rebases onto
|
||||
// it rather than onto a narrower one.
|
||||
Bool MGPipeBarriered(MGPWireOp op, const void* payload, const MGPipeApplierState& st);
|
||||
|
||||
// The record currently being applied, as ApplyOne stamped it. Server-private; `true` on a
|
||||
// server that does not publish kCapRunAheadApply and in every monolith process, which is
|
||||
// what leaves Magma (§6.1) and the push-monolith build on P5C's semantics.
|
||||
Bool MGPipeApplierCurrentRecordIsBarriered();
|
||||
|
||||
// A MAKE-CURRENT, NOT A TEARDOWN - and the distinction is the whole of this function's
|
||||
// contract. It runs on every change of the current GLContext (MGPipeTracker::Update resets
|
||||
// the tracker whenever the context pointer moves, and the emitter calls this from the
|
||||
|
||||
@@ -1055,6 +1055,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// dropped before this function returns, and s1's SessionConsumer::ApplyOne publishes
|
||||
// appliedSeq only after that - so by the time the client is runnable the flag is down.
|
||||
const Client::ClientSession::ScopedApplierEntry insideApplier;
|
||||
// P5e (id), CONTRACT-P5E §2.1 / §4.4: STAMP WHETHER THE CLIENT IS PARKED BEHIND THIS
|
||||
// RECORD, before anything can ask. It is the input to the allocator guard's exemption
|
||||
// (SlotAllocator.cpp) and to every frontend-keyed twin member that survives as monolith
|
||||
// glue (SlotTables.h), and it has to be up before DecodeAndApply because the sinks those
|
||||
// reach are exactly the askers. MGPipeBarriered answers true for every record until ra
|
||||
// lands the wait rule, so this line changes nothing this phase and is the line ra
|
||||
// rebases onto rather than adds.
|
||||
MG_Pipe::MGPipeApplier().CurrentRecordBarriered = MG_Pipe::MGPipeBarriered(
|
||||
static_cast<MG_Pipe::MGPWireOp>(record.kind), record.payload, MG_Pipe::MGPipeApplier());
|
||||
// ORDER IS THE CONTRACT'S: stamp, then apply. The stamp is what makes any server-side
|
||||
// read of gPipeInputs legal at all (PipeApplier.h's block 1), so a record applied
|
||||
// before it aborts on the FIRST field inside SyncRenderState.
|
||||
|
||||
Reference in New Issue
Block a user