mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
Merge commit 'bf5ad470' into codex/p5f-final
This commit is contained in:
@@ -8417,6 +8417,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
);
|
||||
}
|
||||
|
||||
#if !MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5f (fr): no declaration, dispatch-table slot or caller remains for this legacy
|
||||
// helper. Exclude it from the server-capable build; retain non-D-P bytes for G1.
|
||||
GLuint GetBackendProgramId(GLuint program) {
|
||||
if (!MGB_CTX->ValidateProgramName(program)) {
|
||||
MGLOG_E_ONCE("Invalid frontend program object: %u", program);
|
||||
@@ -8445,6 +8448,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
return backendObj->GetBackendProgramId();
|
||||
}
|
||||
#endif
|
||||
|
||||
void Clear(GLbitfield mask) {
|
||||
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
|
||||
|
||||
@@ -329,6 +329,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#endif
|
||||
|
||||
BackendPtr& GetOrCreate(const StatePtr& stateObj) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Check before arm selection: disabling slot tables must not expose the
|
||||
// legacy raw-pointer registry on a transport apply thread.
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("Registry.GetOrCreate(StatePtr)");
|
||||
#endif
|
||||
MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null");
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
@@ -389,6 +394,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// need the twin across another registry call must copy the BackendPtr out (or keep only
|
||||
// the pointee, which is heap-allocated and never moves).
|
||||
BackendPtr* Find(StateObject* stateObj) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Check before arm selection: disabling slot tables must not expose the
|
||||
// legacy raw-pointer registry on a transport apply thread.
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("Registry.Find(StateObject*)");
|
||||
#endif
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
if (EsprytSlotTablesEnabled()) {
|
||||
return m_slotTable.Find(stateObj);
|
||||
@@ -409,8 +419,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return const_cast<StateBackendObjectRegistry*>(this)->Find(stateObj);
|
||||
}
|
||||
|
||||
iterator begin() { return m_entries.begin(); }
|
||||
const_iterator begin() const { return m_entries.begin(); }
|
||||
iterator begin() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("Registry.begin");
|
||||
#endif
|
||||
return m_entries.begin();
|
||||
}
|
||||
const_iterator begin() const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("Registry.begin");
|
||||
#endif
|
||||
return m_entries.begin();
|
||||
}
|
||||
iterator end() { return m_entries.end(); }
|
||||
const_iterator end() const { return m_entries.end(); }
|
||||
|
||||
|
||||
@@ -88,19 +88,11 @@
|
||||
// allocator's own three entries carry, repeated here so the refusal names this surface), and
|
||||
// the split paths resolve through GetOrCreate(handle) / ReleaseByHandle instead.
|
||||
//
|
||||
// P5e (id, CONTRACT-P5E §4.1 / §5.8) SETTLES WHAT HAPPENS TO THE FRONTEND-KEYED HALF: it is
|
||||
// NOT deleted. The push build under Transport=monolith keeps its frontend arms token for token
|
||||
// (ruling 1), which is what the MOBILEGL_PIPE_VERIFY comparator needs and what makes
|
||||
// MOBILEGL_IPC_RUN_AHEAD=0 a pure wait-rule A/B on identical server code. So
|
||||
// GetOrCreate(StatePtr), Find(StateObject*), HandleOf, NoteStateForHandle/StateForHandle,
|
||||
// Entry::stateRef and the resolution memo all survive - as MONOLITH GLUE, each of them a NAMED
|
||||
// FATAL the moment it is reached from an apply thread that is applying an UNBARRIERED record
|
||||
// (§4.4: the client's wait is the only thing that makes such a read stable, and an unbarriered
|
||||
// record has none). The refusal is raised either by the allocator guard, for the three members
|
||||
// that call the allocator, or by MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply for the
|
||||
// three that do not. There is no silent path: the phase's claim is that the twin is resolved
|
||||
// from the handle the record carried, and a surface that quietly answered from a frontend
|
||||
// pointer instead would make that claim untestable.
|
||||
// P5f (fr): the frontend-keyed half survives only as MONOLITH GLUE. HandleOf,
|
||||
// GetOrCreate(StatePtr), NoteStateForHandle and StateForHandle refuse EVERY transport
|
||||
// apply before touching a frontend object, with or without a barrier or named scope.
|
||||
// Find(StateObject*) delegates to HandleOf; handle-only iteration never reads stateRef.
|
||||
// The registry guard is independent of Magma's legacy allocator debt scope.
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
@@ -283,19 +275,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
EnsureProcessTeardownSentinel();
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.1): this overload MINTS - it is monolith glue, and with
|
||||
// an active transport a call from the apply thread is Fatal{RoleViolation,
|
||||
// "MGPipeSlots"} before the allocator is touched. Split paths call the handle
|
||||
// overload below. (The check also lives at the allocator's own three entries; it
|
||||
// is repeated at this entry so the refusal names this surface even if the entry
|
||||
// set changes.)
|
||||
//
|
||||
// P5e (id, §4.4): that one call is also this member's unbarriered-apply refusal.
|
||||
// The guard exempts a probe only inside a named scope AND while the record being
|
||||
// applied is barriered, so reaching a MINT from an unbarriered apply aborts here
|
||||
// whatever scope is open - which is what "kept only as monolith glue" has to mean
|
||||
// if it is to be checkable.
|
||||
MG_Pipe::MGPipeRefuseAllocatorFromApplyThread("GetOrCreate(StatePtr)");
|
||||
// This overload mints from frontend identity and is monolith-only. Refuse
|
||||
// before either the client object or allocator is touched, even when a
|
||||
// legacy named scope is open and the current record is barriered.
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("GetOrCreate(StatePtr)");
|
||||
#endif
|
||||
|
||||
const MG_Pipe::MGPipeHandle handle =
|
||||
@@ -407,25 +390,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return entry->Live ? entry->Gen : 0;
|
||||
}
|
||||
|
||||
// P5c (hd): remember the frontend object a HANDLE-keyed twin was synced from. The
|
||||
// minting overload sets stateRef itself; the handle overload cannot (no object
|
||||
// crosses), so a caller that legitimately holds the object - the record-driven sync,
|
||||
// which arrived holding it through the object-class barrier-pulled rows - notes it
|
||||
// here. It is what lets a later handle-only resolution (P5c's named blit, and P5e's
|
||||
// re-typed ForEachLive) reach the frontend object the twin's sync body still walks,
|
||||
// without probing the client's slot allocator (T2). Same liveness rules as the minted
|
||||
// stateRef: never an identity test, never read to decide the slot is dead.
|
||||
//
|
||||
// P5e (id): the pair is now compiled under MOBILEGL_PIPE_PUSH rather than
|
||||
// MOBILEGL_BUILD_DISAGGREGATED, because ForEachLive's caller needs it in the
|
||||
// push-monolith build too, where the note is simply always present. And each half is a
|
||||
// NAMED FATAL from an unbarriered apply (§4.4): they answer from a frontend SharedPtr
|
||||
// that only the client's wait pins, so without that wait the object they hand back may
|
||||
// already be the client's next one. Neither touches the allocator, so the allocator
|
||||
// guard never sees them - this is their own refusal.
|
||||
// Remember/read the frontend object last used by the monolith sync path.
|
||||
// Handles supplied to the server never need this weak state. Neither method
|
||||
// touches the allocator, so each carries its own unconditional apply guard.
|
||||
void NoteStateForHandle(MG_Pipe::MGPipeHandle handle, const StatePtr& stateObj) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply("NoteStateForHandle");
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("NoteStateForHandle");
|
||||
#endif
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return;
|
||||
Entry* const entry = EntryOrNull(handle.Slot);
|
||||
@@ -437,7 +407,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// this answers only "which object was this twin last synced from".
|
||||
StatePtr StateForHandle(MG_Pipe::MGPipeHandle handle) const {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromUnbarrieredApply("StateForHandle");
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("StateForHandle");
|
||||
#endif
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
|
||||
const Entry* const entry = EntryOrNull(handle.Slot);
|
||||
@@ -501,12 +471,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
MG_Pipe::MGPipeHandle HandleOf(const StateObject* stateObj) const {
|
||||
if (stateObj == nullptr) return MG_Pipe::kMGPipeNullHandle;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd): same guard as the minting overload - a lifetime-id probe from the
|
||||
// apply thread is Fatal{RoleViolation, "MGPipeSlots"} with an active transport.
|
||||
// P5e (id, §4.4): and now also whenever the record being applied is UNBARRIERED,
|
||||
// scope or no scope. This member and Find(StateObject*) below it are the two the
|
||||
// per-family packages retire by passing the handle the record carried instead.
|
||||
MG_Pipe::MGPipeRefuseAllocatorFromApplyThread("HandleOf");
|
||||
// Resolve frontend identity only in the monolith path. A cache hit is
|
||||
// subject to the same guard as a fresh allocator lookup.
|
||||
MG_Pipe::MGPipeRefuseFrontendKeyedRegistryFromApplyThread("HandleOf");
|
||||
#endif
|
||||
const Uint64 lifetimeId = stateObj->GetLifetimeId();
|
||||
if (lifetimeId == m_memoLifetimeId) return m_memoHandle;
|
||||
|
||||
@@ -11,9 +11,6 @@
|
||||
|
||||
#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>
|
||||
@@ -32,129 +29,32 @@ namespace MobileGL::MG_Pipe {
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) 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;
|
||||
}
|
||||
// P5f (fr): all named allocator exemptions are retired. A barrier can
|
||||
// stabilize a client allocator but cannot make it server-owned memory.
|
||||
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; 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",
|
||||
"its side of a real split. No scope or backend exempts a barriered record "
|
||||
"(barriered=%d)",
|
||||
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;
|
||||
// P5f (fr): all frontend-identity registry surfaces are monolith glue, including
|
||||
// the ones that do not touch the allocator. Neither a wait nor a named scope can
|
||||
// make a frontend SharedPtr exist in a separate server process.
|
||||
void MGPipeRefuseFrontendKeyedRegistryFromApplyThread(const char* entry) {
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
if (!MG_Remote::Server::ServerLoop::OnApplyThread()) 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",
|
||||
"BackendSlotTable::%s. Frontend-identity registry operations are monolith-only, "
|
||||
"including barriered records; resolve the twin from the record's handle instead",
|
||||
entry);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
namespace {
|
||||
// NOT thread_local any more, and not atomic either (P5d round 3, package D).
|
||||
//
|
||||
// WHY THERE IS NO DATA RACE. Both depths have exactly ONE reader in the whole tree:
|
||||
// MGPipeRefuseAllocatorFromApplyThread above, whose first two lines return unless the
|
||||
// transport is active AND ServerLoop::OnApplyThread() is true. So the only thread whose
|
||||
// depth could ever change an answer is the apply thread - and the four scope bodies
|
||||
// below now increment and decrement ONLY when OnApplyThread() says so. That makes the
|
||||
// apply thread the single writer and the single reader of both counters; no other
|
||||
// thread touches them, and a monolith process never even reads them (the guard's first
|
||||
// line returns on TransportMode::Monolith and there is no apply thread to make the
|
||||
// second true). Single-writer-single-reader on one thread needs no lock, no atomic and
|
||||
// no TLS.
|
||||
//
|
||||
// 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, 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.
|
||||
//
|
||||
// The query's meaning ON THE APPLY THREAD is unchanged, which is the only meaning
|
||||
// the guard reads. Off it the answer is now always false, and the query is named
|
||||
// ActiveOnApplyThread() rather than Active() so that a reader who needs the other
|
||||
// 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_magmaP7AllocatorDebtScopeDepth = 0;
|
||||
}
|
||||
|
||||
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_magmaP7AllocatorDebtScopeDepth;
|
||||
}
|
||||
|
||||
MagmaP7AllocatorDebtScope::~MagmaP7AllocatorDebtScope() {
|
||||
if (m_counted) --g_magmaP7AllocatorDebtScopeDepth;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Same counter, same argument, same single reader - see the block above.
|
||||
Uint32 g_frontendKeyedRegistryScopeDepth = 0;
|
||||
}
|
||||
|
||||
MGPipeFrontendKeyedRegistryScope::MGPipeFrontendKeyedRegistryScope()
|
||||
: m_counted(MG_Remote::Server::ServerLoop::OnApplyThread()) {
|
||||
if (m_counted) ++g_frontendKeyedRegistryScopeDepth;
|
||||
}
|
||||
|
||||
MGPipeFrontendKeyedRegistryScope::~MGPipeFrontendKeyedRegistryScope() {
|
||||
if (m_counted) --g_frontendKeyedRegistryScopeDepth;
|
||||
}
|
||||
|
||||
Bool MGPipeFrontendKeyedRegistryScope::ActiveOnApplyThread() {
|
||||
return g_frontendKeyedRegistryScopeDepth != 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
@@ -422,6 +322,12 @@ namespace MobileGL::MG_Pipe {
|
||||
}
|
||||
|
||||
MGPipeSlotAllocator& MGPipeSlots() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// Guard the client singleton itself too: Allocate/IsLive/HighWater and the
|
||||
// bookkeeping accessors must not bypass the original three method guards.
|
||||
// Backend-private allocator instances do not use this accessor.
|
||||
MGPipeRefuseAllocatorFromApplyThread("client-singleton");
|
||||
#endif
|
||||
// NEVER DESTROYED, deliberately (one allocation for the life of the process). A
|
||||
// frontend object's destructor reaches this allocator - ~BufferObject through
|
||||
// MGPipeEmitResourceDestroyAndFree, ~VertexArrayObject through the death notice - and
|
||||
|
||||
@@ -166,119 +166,41 @@ namespace MobileGL::MG_Pipe {
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// P5c (hd, CONTRACT-P5C §3.1 / §6 layer 1): with an active transport this allocator is a
|
||||
// CLIENT-only surface. Acquire, FindByLifetimeId and Free called from the apply thread -
|
||||
// CLIENT-only surface. Its singleton accessor and original three method guards reject apply -
|
||||
// i.e. a server that resolves or mints handles off a frontend object's lifetime id (T2),
|
||||
// which is memory that will not exist on its side of a real split - are
|
||||
// Fatal{RoleViolation, "MGPipeSlots"}. Compiled out entirely outside split builds, so the
|
||||
// pull build's bytes do not move (G1).
|
||||
void MGPipeRefuseAllocatorFromApplyThread(const char* entry);
|
||||
|
||||
// ---- 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.
|
||||
// Shared predicate for remaining field/verb guards. Registry and allocator
|
||||
// guards below refuse both barriered and unbarriered transport apply.
|
||||
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);
|
||||
// P5f (fr): frontend-object twin lookup, minting and weak-state access are refused
|
||||
// on EVERY transport apply, including barriered records and both named scopes.
|
||||
// The handle overloads remain server-local; the frontend overloads remain monolith
|
||||
// glue. The registry guard is independent of the allocator guard.
|
||||
void MGPipeRefuseFrontendKeyedRegistryFromApplyThread(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
|
||||
// ServerLoop::OnApplyThread() is true, so a depth kept on any OTHER thread could never
|
||||
// change an answer - it was pure cost. The query is called ActiveOnApplyThread() and not
|
||||
// Active() BECAUSE OF THAT (review round 3): a name that promised "is a scope open" would
|
||||
// now be quietly answering "is a scope open ON THE APPLY THREAD", and the next reader to
|
||||
// come along - a P4b/P7 probe deciding on the GL thread whether to emit a record - would
|
||||
// read a truthful-looking false and take the wrong branch with nothing to warn it. The name
|
||||
// carries the precondition so a second reader has to notice it. m_counted remembers what the constructor decided so
|
||||
// the destructor undoes exactly what the constructor did; asking the predicate twice would
|
||||
// leak a count for a scope that outlived the apply thread. On the GL thread (and in every
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
// P5f (fr): these historical scope names remain greppable markers around
|
||||
// monolith glue until P3b/P4b remove those bodies. They hold no state and grant
|
||||
// no exemption: allocator and frontend-registry access always refuse apply.
|
||||
class MagmaP7AllocatorDebtScope {
|
||||
public:
|
||||
MagmaP7AllocatorDebtScope();
|
||||
~MagmaP7AllocatorDebtScope();
|
||||
MagmaP7AllocatorDebtScope() = default;
|
||||
~MagmaP7AllocatorDebtScope() = default;
|
||||
MagmaP7AllocatorDebtScope(const MagmaP7AllocatorDebtScope&) = delete;
|
||||
MagmaP7AllocatorDebtScope& operator=(const MagmaP7AllocatorDebtScope&) = delete;
|
||||
static Bool ActiveOnApplyThread();
|
||||
|
||||
private:
|
||||
Bool m_counted;
|
||||
};
|
||||
|
||||
// The SECOND named exemption family (CONTRACT-P5C §5.4): the frontend-keyed twin
|
||||
// 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
|
||||
// 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"}.
|
||||
//
|
||||
// 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.
|
||||
class MGPipeFrontendKeyedRegistryScope {
|
||||
public:
|
||||
MGPipeFrontendKeyedRegistryScope();
|
||||
~MGPipeFrontendKeyedRegistryScope();
|
||||
MGPipeFrontendKeyedRegistryScope() = default;
|
||||
~MGPipeFrontendKeyedRegistryScope() = default;
|
||||
MGPipeFrontendKeyedRegistryScope(const MGPipeFrontendKeyedRegistryScope&) = delete;
|
||||
MGPipeFrontendKeyedRegistryScope& operator=(const MGPipeFrontendKeyedRegistryScope&) = delete;
|
||||
static Bool ActiveOnApplyThread();
|
||||
|
||||
private:
|
||||
Bool m_counted;
|
||||
};
|
||||
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include <MG_Pipe/PipeMutation.h>
|
||||
#include <MG_Pipe/PipeApply.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
#include <MG_Backend/DirectGLES/Managers.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
#include <Init.h>
|
||||
#include <MG_Impl/EGLImpl/EGLImpl.h>
|
||||
@@ -1898,29 +1899,9 @@ MGL_BUFFER_GUARD_TEST(BufferSyncPersistentMappedRangeFromTheApplyThreadIsFatalBy
|
||||
MGL_BUFFER_GUARD_TEST(BufferHasDefinedContentFromTheApplyThreadIsFatalByName, 707, (void)buffer.HasDefinedContent())
|
||||
#undef MGL_BUFFER_GUARD_TEST
|
||||
|
||||
// P5d round 3 (package D): the two NAMED EXEMPTION scopes of CONTRACT-P5C §3.1 / §5.4 stopped
|
||||
// keeping their depth in a thread_local and now count ONLY while ServerLoop::OnApplyThread() is
|
||||
// true (SlotAllocator.cpp). The claim that makes that legal is "the apply-thread guard is the
|
||||
// counter's only reader, so a depth kept on any other thread cannot change an answer" - and
|
||||
// these three cases are that claim in executable form. They are the red-once for the change:
|
||||
//
|
||||
// (a) drop the `if (m_counted)` from the constructor (or make it unconditional again on a
|
||||
// thread that is not the apply thread) and (c) goes red - a GL-thread scope would start
|
||||
// exempting the apply thread, which is exactly the leak the thread_local used to prevent;
|
||||
// (b) drop the increment altogether and (a) goes red - the exemption stops exempting and
|
||||
// the debt's own sites (DirectGLES' HandleOf probes) abort the server;
|
||||
// (c) drop the guard's `MagmaP7AllocatorDebtScope::ActiveOnApplyThread()` /
|
||||
// `FrontendKeyedRegistryScope::ActiveOnApplyThread()` rows and (b) - the control - stops
|
||||
// being the only aborting arm.
|
||||
//
|
||||
// P5e (id), CONTRACT-P5E §4.4 REPLACED (b). The case that used to sit there -
|
||||
// `AnAllocatorProbeInsideAnExemptionScopeOnTheApplyThreadIsAllowed` - asserted that a scope
|
||||
// exempts UNCONDITIONALLY, which is exactly the rule §4.4 retires: an exemption is a debt the
|
||||
// CLIENT'S WAIT pays for, so it holds only while the record being applied is barriered. Its
|
||||
// replacement below drives the same scope with the barriered flag forced false and expects the
|
||||
// abort. The green half of the old case did not need a test of its own: every Espryt
|
||||
// `integration-split` entry runs the frontend-keyed scope's surviving sites on the apply thread
|
||||
// behind barriered records, so a guard that stopped exempting would take the whole lane down.
|
||||
// Historical P5d/P5e probes remain named for compatibility. P5f retires both
|
||||
// scope exemptions, including barriered records; all allocator access on apply is
|
||||
// client-memory access regardless of whether that memory happens to be stable.
|
||||
TEST(RemoteGuards, AnAllocatorProbeFromTheApplyThreadOutsideEveryScopeIsFatalByName) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
@@ -1935,24 +1916,8 @@ TEST(RemoteGuards, AnAllocatorProbeFromTheApplyThreadOutsideEveryScopeIsFatalByN
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
// P5e (id) RED-ONCE, CONTRACT-P5E §4.4. The exemption scope is OPEN and the probe is the one
|
||||
// the debt is named for - `HandleOf`, the frontend-keyed registry's own entry - and it still
|
||||
// aborts, because the record being applied is unbarriered: the client is running ahead and the
|
||||
// allocator's free list and lifetimeId -> slot map are moving under the read.
|
||||
//
|
||||
// THE HOOK IS THE APPLIER FIELD ITSELF and that is deliberate: `CurrentRecordBarriered` is what
|
||||
// PipeApplier::ApplyOne stamps from MGPipeBarriered() on every record, so forcing it here drives
|
||||
// the production path rather than a test-only branch beside it. Until ra lands the wait rule
|
||||
// MGPipeBarriered answers true for every record, so this is the ONLY way to reach the arm - and
|
||||
// the arm has to exist before ra, because ra is what makes it reachable in production.
|
||||
//
|
||||
// The red: put §4.4's `if (MGPipeApplierCurrentRecordIsBarriered())` back to an unconditional
|
||||
// exemption in MGPipeRefuseAllocatorFromApplyThread and this case stops aborting - red by
|
||||
// expectation, which is the shape a guard case fails in.
|
||||
// EVERYTHING BUT THE PROBE RUNS ON THE CLIENT THREAD, which is the buffer/texture guard cases'
|
||||
// shape one block down and is load-bearing here rather than tidy: a TextureObject2D's
|
||||
// constructor MINTS a resource handle, and building it on the apply thread would abort at the
|
||||
// mint with the same diagnostic - a case that passes for a reason other than the one it names.
|
||||
// Construct on the client and run only HandleOf on apply. The false barrier stamp
|
||||
// keeps the original P5e scenario; the tests below add the old barriered loophole.
|
||||
TEST(RemoteGuards, AnAllocatorProbeInsideAnExemptionScopeFromTheApplyThreadIsFatalEvenAtTheOldDebtSites) {
|
||||
struct Probe {
|
||||
TestTextureTable* table;
|
||||
@@ -1980,11 +1945,123 @@ TEST(RemoteGuards, AnAllocatorProbeInsideAnExemptionScopeFromTheApplyThreadIsFat
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
|
||||
// THE ONE THAT PINS THE SINGLE-WRITER ARGUMENT. The scope is open on the GL thread for the
|
||||
// whole of the posted request; the apply thread's probe must still abort, because the exemption
|
||||
// belongs to the thread that entered the scope and never to another one. With the depth kept in
|
||||
// a plain counter that every thread incremented, this case would go green - and a real
|
||||
// apply-thread violation would be silently exempted for as long as any GL thread held a scope.
|
||||
// P5f (fr): barriers and old named scopes no longer admit frontend identity.
|
||||
// Construct the object and table on the client so each death comes from the exact
|
||||
// probed member, not a constructor's resource_create or a missing record.
|
||||
TEST(RemoteGuards, BarrieredLegacyScopesCannotExemptAllocator) {
|
||||
for (const auto backend : {BackendType::DirectGLES, BackendType::DirectVulkan}) {
|
||||
const auto child = RunInChild([backend] {
|
||||
StartControlSession();
|
||||
auto selectedBackend = backend;
|
||||
Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void* self) -> MobileGLResult {
|
||||
MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true);
|
||||
MG_Config::ActiveBackendType = *static_cast<BackendType*>(self);
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope registryScope;
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaScope;
|
||||
// HighWater had no per-method guard in the old implementation.
|
||||
(void)MGPipeSlots().HighWater(MGPipeKind::Texture);
|
||||
return MOBILEGL_OK;
|
||||
}, &selectedBackend);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, BarrieredFrontendRegistryMembersRefuseBothLegacyScopes) {
|
||||
struct Probe {
|
||||
TestTextureTable* table;
|
||||
SharedPtr<MG_State::GLState::TextureObject2D>* texture;
|
||||
MGPipeHandle handle;
|
||||
int operation;
|
||||
};
|
||||
const char* members[] = {"HandleOf", "GetOrCreate(StatePtr)", "NoteStateForHandle", "StateForHandle"};
|
||||
for (int operation = 0; operation != 4; ++operation) {
|
||||
SCOPED_TRACE(members[operation]);
|
||||
const auto child = RunInChild([operation] {
|
||||
StartControlSession();
|
||||
auto texture = MakeShared<MG_State::GLState::TextureObject2D>(47);
|
||||
TestTextureTable table;
|
||||
table.GetOrCreate(texture) = MakeShared<FakeTwin>();
|
||||
Probe probe{&table, &texture, table.HandleOf(texture.get()), operation};
|
||||
Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void* self) -> MobileGLResult {
|
||||
auto& probe = *static_cast<Probe*>(self);
|
||||
MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true);
|
||||
MG_Config::ActiveBackendType = BackendType::DirectVulkan;
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope registryScope;
|
||||
const MG_Pipe::MagmaP7AllocatorDebtScope magmaScope;
|
||||
switch (probe.operation) {
|
||||
case 0: probe.table->HandleOf(probe.texture->get()); break;
|
||||
case 1: probe.table->GetOrCreate(*probe.texture); break;
|
||||
case 2: probe.table->NoteStateForHandle(probe.handle, *probe.texture); break;
|
||||
case 3: probe.table->StateForHandle(probe.handle); break;
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}, &probe);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
EXPECT_NE(child.Log.find(std::string{"BackendSlotTable::"} + members[operation]), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, LegacyRegistryWrapperCannotExposeFrontendKeysOnApply) {
|
||||
using Registry = MG_Backend::DirectGLES::StateBackendObjectRegistry<
|
||||
MG_State::GLState::TextureObject2D, FakeTwin, MGPipeKind::Texture>;
|
||||
struct Probe { Registry* registry; SharedPtr<MG_State::GLState::TextureObject2D>* texture; int operation; };
|
||||
for (int operation = 0; operation != 3; ++operation) {
|
||||
const auto child = RunInChild([operation] {
|
||||
StartControlSession();
|
||||
auto texture = MakeShared<MG_State::GLState::TextureObject2D>(48);
|
||||
Registry registry;
|
||||
Probe probe{®istry, &texture, operation};
|
||||
Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void* self) -> MobileGLResult {
|
||||
auto& probe = *static_cast<Probe*>(self);
|
||||
MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true);
|
||||
const MG_Pipe::MGPipeFrontendKeyedRegistryScope scope;
|
||||
switch (probe.operation) {
|
||||
case 0: probe.registry->Find(probe.texture->get()); break;
|
||||
case 1: probe.registry->GetOrCreate(*probe.texture); break;
|
||||
case 2: (void)probe.registry->begin(); break;
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}, &probe);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}");
|
||||
EXPECT_NE(child.Log.find("BackendSlotTable::Registry."), std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RemoteGuards, HandleRegistryMembersWorkOnBarrieredAndUnbarrieredApply) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void*) -> MobileGLResult {
|
||||
TestTextureTable table;
|
||||
for (const bool barriered : {true, false}) {
|
||||
MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(barriered);
|
||||
const MGPipeHandle handle{71, barriered ? 1u : 2u};
|
||||
auto& twin = table.GetOrCreate(handle);
|
||||
twin = MakeShared<FakeTwin>();
|
||||
twin->marker = 19;
|
||||
auto* found = table.FindByHandle(handle);
|
||||
if (!found || !*found || (*found)->marker != 19) _exit(81);
|
||||
unsigned live = 0;
|
||||
table.ForEachLive([&](MGPipeHandle actual, const auto& backend) {
|
||||
if (actual.Slot != handle.Slot || actual.Gen != handle.Gen || backend->marker != 19) _exit(82);
|
||||
++live;
|
||||
});
|
||||
if (live != 1 || !table.ReleaseByHandle(handle) || table.FindByHandle(handle)) _exit(83);
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}, nullptr);
|
||||
ClientSessionInstance().Stop();
|
||||
});
|
||||
ASSERT_TRUE(WIFEXITED(child.Status)) << child.Log;
|
||||
EXPECT_EQ(WEXITSTATUS(child.Status), 0) << child.Log;
|
||||
}
|
||||
|
||||
// A marker held by the GL thread never affects the apply guard either.
|
||||
TEST(RemoteGuards, AnExemptionScopeHeldOnTheGLThreadDoesNotExemptTheApplyThread) {
|
||||
const auto child = RunInChild([] {
|
||||
StartControlSession();
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# P5f / fr — registry 身份边收口
|
||||
|
||||
> 分支 `codex/p5f-fr`,基线 `4667e13b`。工作树 `MobileGL-p5f-fr`;
|
||||
> 独立 Linux 验证树 `/home/swung/w7/p5f-fr`。这是波次 3 的 fr 包,
|
||||
> fe/fm 已迁移的 CopyImage 和 storage-block 接线不重复计数。
|
||||
|
||||
## 1. 结果与边界
|
||||
|
||||
所有 transport apply 对 client allocator 的访问都具名拒绝,设障、后端种类和历史
|
||||
scope 均不再豁免。`MGPipeSlots()` 本身新增守卫,覆盖旧三方法守卫之外的
|
||||
`Allocate`、`IsLive`、`HighWater` 等入口;backend 私有 allocator 不经这个 singleton。
|
||||
|
||||
`BackendSlotTable` 的 `GetOrCreate(StatePtr)`、`HandleOf`、`NoteStateForHandle`、
|
||||
`StateForHandle` 在所有 apply 上拒绝 frontend 身份;`Find(StateObject*)` 经 `HandleOf`。
|
||||
上层 `StateBackendObjectRegistry` 的 frontend lookup/mint/iterator 在臂选择之前检查,
|
||||
不能靠关闭 slot-table 位退回裸地址 map。按 handle 的查找、创建、遍历、释放仍正常工作。
|
||||
拒绝为 `Fatal{RoleViolation, "MGPipeSlots"}`,日志还包含具体成员名。
|
||||
|
||||
两种历史 scope 现在是无状态标记,不持有深度、不产生豁免。P3b/P4b 可随 monolith
|
||||
glue 一起删除这些标记;P5f 不删掉对应的 monolith 功能来消除 grep 结果。
|
||||
|
||||
自由函数 `DirectGLES::GetBackendProgramId(GLuint)` 全树无声明、调用者或 dispatch 槽。
|
||||
本包以 `!MOBILEGL_BUILD_DISAGGREGATED` 隔离,D-P 中不再存在;保留非 D-P 函数体,
|
||||
避免仅为删除死代码改变 pull 的 `.text` 与符号。它不等于仍被大量使用的
|
||||
`BackendProgramObjectImpl::GetBackendProgramId() const` 成员。
|
||||
|
||||
## 2. 原 13 个 DirectGLES scope 的重新核对
|
||||
|
||||
这里按函数名定位,不沿用 f0 的旧行号。家族位关闭导致的 legacy 回退不视为安全的 wire
|
||||
功能;新的无条件身份守卫和字段收口会拒绝它,不能用“client 已停在 barrier”解释放行。
|
||||
|
||||
| 原 scope 所在函数 | 当前 transport 路线 / 阻断点 |
|
||||
|---|---|
|
||||
| `SyncTextureObjectToBackend` | `OnResourceCopyRegion` 已由 fe/fm 将 `Src/Dst` 放入 `CopyImageEndpoint.TextureHandle`,GLES endpoint 走 `SyncTextureToBackendByHandle`;旧对象 overload 留 monolith。 |
|
||||
| `SyncCurrentFBO` | 先走 `SyncCurrentFBOByRecord`;开启 family 后的 decline 经 `RefuseFramebufferBindingSlotRead`,不会落入对象 lookup。 |
|
||||
| `ResolveGlobalConstantsRecord(program)` | transport 的调用点用 `ResolveGlobalConstantsRecordForHandle`;旧对象 overload 留 monolith。 |
|
||||
| `SyncCurrentProgram(program)` | draw/dispatch 经 `ProgramHandleArm()` 选 `SyncCurrentProgramByHandle`。 |
|
||||
| `BindCurrentFBO` | record 的 `Fbo` 经 `FindByHandle`;mandatory record 臂 decline 即拒绝。 |
|
||||
| `ResolveAndBindUnitTextures` | `UnitTexturesByHandle()` 的 record 分支先行返回;legacy unit pass 留 monolith。 |
|
||||
| `ResolveUnitSamplerBackend` | transport 在 scope 前以 `BindSamplerStates.Count` 拒绝 identity sampler 家族。 |
|
||||
| `BindCurrentProgramWithResources` | `handleArm` 经句柄 stash / `ResolveProgramTwin`,对象 registry 在另一臂。 |
|
||||
| `GetCurrentBackendProgram` | `ProgramHandleArm` 在旧 scope 前返回。 |
|
||||
| 自由函数 `GetBackendProgramId(GLuint)` | 无调用者;本包从 D-P 编译中排除。 |
|
||||
| `ScopedDetachedTextureFramebufferAttachments(texture)` | transport mipmap 用 handle 构造与 record reverse-index;对象构造及 `StateForHandle` 留 monolith。 |
|
||||
| `ShaderStorageBlockBinding` | fe 已用 `VerbStorageBlockProgram` → `FindByHandle` → native program id,并先行返回;旧 GL-name 查对象路径留 monolith。 |
|
||||
| `GetTexImage` | wire 槽仍在 `MGR_UNMIGRATED_TAIL_SLOTS`,`Fatal{UnmigratedVerb, "GetTexImage"}`;不能因为本包关闭 scope 就宣称 P9 功能已实现。 |
|
||||
|
||||
## 3. Managers 的 11 个 scope 与普查遗漏
|
||||
|
||||
`HandleOfBuffer` 的 frontend overload 在 legacy buffer path;transport buffer consumers
|
||||
用 handle 入口。`HandleOfSamplerViewForTexture` 全树没有调用者,仍是 monolith glue。
|
||||
`RequireImageBindableStorage` 的 object overload 在 legacy texture path;transport 使用
|
||||
`RequireImageBindableStorageByHandle`。`ResolveOwnRecord` 在 transport twin 已带
|
||||
`m_pushedSyncHandle`,不进入对象 lookup。
|
||||
|
||||
`SyncAttachmentObject` 的 texture/renderbuffer 两站和 FBO verify 的两站位于 object
|
||||
`BackendFramebufferObject::SyncToBackend`;transport 使用 `SyncToBackendByHandle`。
|
||||
program serial 盖章和 renderbuffer record 解析位于各自 object `SyncToBackend`;
|
||||
transport 的 by-handle overload 不问 frontend identity。
|
||||
|
||||
**第 11 项 sampler registration probe 不能照 f0 写成不可达。** transport 的
|
||||
`BindCurrentProgramWithResources` 在 depth-texture、普通 `sampler2D`、需要强制 nearest
|
||||
的组合下实际调用 `GetRawDepthFetchSampler()`;该 helper 原来创建 `SamplerObject`,
|
||||
setter 还会读 client context,随后 `BackendSamplerObject::SyncToBackend` 的无 CSO 分支
|
||||
进入这个 registration probe。此遗漏已交 fs 包改为 server native sampler,并补世代测试。
|
||||
fr 关闭所有 registry 豁免后,这个遗漏不会再静默放行。
|
||||
|
||||
同时发现 `VulkanRenderer::Initialize()` 仍无条件创建 blit/depth-mipmap 隐藏
|
||||
frontend program;相应两个 shutdown scope 不能当作不可达依据。已交 fv 包将 transport
|
||||
初始化改道。原有 host Magma 用例通过不能代替完整 surface 初始化证据;本报告不声称
|
||||
另有已经核实的 headless 初始化豁免。fr 仍无条件关闭 Magma allocator 豁免。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
- 行为代码 `8377e6cf` 的独立 split `RemoteClientTest` 构建成功,新增四个测试;
|
||||
`RemoteGuards` 44/44 通过,完整 executable 147/147 通过(`fr-remoteclient-full.log`)。
|
||||
- `BarrieredLegacyScopesCannotExemptAllocator` 在 GLES / Vulkan 两 backend 都打开两种旧
|
||||
scope,然后实际调用 client singleton 的 `HighWater`,必须在访问前具名 abort。
|
||||
- `BarrieredFrontendRegistryMembersRefuseBothLegacyScopes` 对四个身份入口逐项检查;对象
|
||||
和表在 client 创建,只有被测操作运行在 apply,日志核对具体成员名,避免在无关构造处死掉。
|
||||
- wrapper 测试覆盖 frontend `Find`、mint 和 iterator,核对 wrapper 自己的 marker。
|
||||
- handle 正对照在 barriered / unbarriered 两种记录下验证创建、查找、遍历、释放。
|
||||
- **真实 red-once**:临时在两个 production guard 恢复“当前记录 barriered 就放行”的旧
|
||||
条件,构建并执行两条 barriered 测试,**2/2 真红**,失败为预期 abort 没有发生;恢复健康
|
||||
源码并重建后守卫测试全绿。日志为 `fr-red-once.log`、`fr-restored-green.log`,临时修改未提交。
|
||||
- 非 D-P 行为改动全部由预处理隔离。pull / push、G1/G2/G14、完整 unit/GPU、strict 与
|
||||
dualblock 在 fs/fr/fv 合并树由集成者执行;本包不拿单测结果冒充整阶段门。
|
||||
|
||||
Linux 新树的子模块递归初始化遇到本地缺少 apitrace nested commit,随后复用了现有
|
||||
`p5f-int` 的依赖与 fixture 文件;未运行 `git lfs pull/checkout`,未修改参考树。
|
||||
|
||||
Reference in New Issue
Block a user