From f5bd1a041238ef00e5fbc1890128c41527cf219b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 6 Sep 2026 13:12:59 -0400 Subject: [PATCH] [Test] (Espryt): drive the acquire / look up / delete / re-acquire walk through the real registry for all six re-keyed kinds - The D13 "must not break" pins made ZERO slot acquisitions: the scratch-FBO scrub and the three context-generation guards build their twins with MakeShared directly or under ScopedStateGuardMocks, so they never reach StateBackendObjectRegistry and pass identically on both arms. The four acquisitions in the binary were all kind Texture, and the eleven DirectGLESSlotTable cases drive BackendSlotTable directly on the throwaway kinds Query and Fence - so they would pass had the six registries never been re-keyed. Framebuffer, Renderbuffer, SamplerCso, ShaderCso and VertexElementsCso had no case that could go red for the switch-over. - EverySwitchedOverKindResolvesItsTwinThroughTheHandleArm walks all six through the real registry global the shipping paths call: GetOrCreate mints a non-null {slot, gen}, Find and FindByHandle name the same twin storage, the object's own destructor notice frees the slot with no sweep, and the successor lands on the freed slot with a moved Gen while the predecessor's handle resolves to nothing. A kind still on the legacy arm answers the null handle and fails the assertion by name. - Evidence: gdb breakpoint on MGPipeSlotAllocator::Acquire counts 12 hits for this case alone in build-push - six kinds times the two objects each - against 4 for the whole rest of the binary. It skips visibly on the legacy arm and in the pull build (G2 parity). --- MobileGL/MG_Test/SanityTest.cpp | 98 +++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 3ae3531a..93cc3154 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -3552,6 +3552,100 @@ TEST(DirectGLESSlotTable, TheTwinRegistryCasesInThisBinaryRunOnTheHandleArm) { 0ull); } +namespace { + // One kind's worth of the walk the re-key exists for - acquire, look up by object, look up + // by handle, delete, re-acquire - driven through the REAL registry global that every + // shipping path uses, and therefore through StateBackendObjectRegistry's arm dispatch. + // + // That "through the real registry" is the whole point of this helper. The eleven + // DirectGLESSlotTable cases above drive BackendSlotTable directly on the throwaway kinds + // Query and Fence, so they would pass unchanged had the six registries never been re-keyed; + // and of the D13 "must not break" cases only the sampled-set staleness walk makes a twin at + // all, all four of them of kind Texture. Five of the six re-keyed kinds therefore had no + // case that could go red for the switch-over. This is that case. + // + // No backend twin is constructed: every one of the six twin classes generates a driver id + // in its constructor, and none of that is what was re-keyed. What is asserted instead is + // that GetOrCreate, Find(object) and FindByHandle(handle) all name the SAME twin storage, + // that the handle is a real {slot, gen} rather than the null handle the legacy arm answers, + // and that a successor object landing on the freed slot gets a different Gen while the + // predecessor's handle resolves to nothing. + template + void ExpectTheHandleArmDrivesThisKind(const char* kindName, Registry& registry, MakeObject make) { + using namespace MobileGL; + + auto first = make(); + ASSERT_NE(first, nullptr) << kindName; + auto& firstTwin = registry.GetOrCreate(first); + ASSERT_NE(MG_State::GLState::GetStateObjectDeathOps(), nullptr) + << kindName << ": twinning an object did not install the death-notice consumer"; + + const MG_Pipe::MGPipeHandle firstHandle = registry.HandleOf(first.get()); + ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(firstHandle)) + << kindName << ": GetOrCreate on the real registry minted no handle, so this kind is " + "not running on the {slot, gen} arm at all"; + EXPECT_EQ(registry.Find(first.get()), &firstTwin) + << kindName << ": Find resolved a different twin slot than GetOrCreate handed back"; + EXPECT_EQ(registry.FindByHandle(firstHandle), &firstTwin) + << kindName << ": the handle does not address the twin GetOrCreate handed back"; + + // The frontend object dies. NOTHING below sweeps - the destructor's own notice is the + // only thing that can free the slot, which is what makes this the e2/e3 pair end to end. + const Uint64 firstLifetimeId = first->GetLifetimeId(); + first.reset(); + EXPECT_EQ(registry.FindByHandle(firstHandle), nullptr) + << kindName << ": the twin outlived the announced death of its object"; + EXPECT_FALSE(registry.DestroyByLifetimeId(firstLifetimeId)) + << kindName << ": the slot was still held after its object announced its death"; + + auto second = make(); + ASSERT_NE(second, nullptr) << kindName; + auto& secondTwin = registry.GetOrCreate(second); + const MG_Pipe::MGPipeHandle secondHandle = registry.HandleOf(second.get()); + ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(secondHandle)) << kindName; + EXPECT_EQ(secondHandle.Slot, firstHandle.Slot) + << kindName << ": the freed slot was not handed back, so this walk did not exercise " + "the recycle it exists to test"; + EXPECT_NE(secondHandle.Gen, firstHandle.Gen) + << kindName << ": Gen did not move on slot reuse - the predecessor's handle would " + "resolve to the successor's twin, which is the ABA the address key " + "could only paper over"; + EXPECT_EQ(registry.FindByHandle(firstHandle), nullptr) + << kindName << ": the STALE handle resolved to a twin"; + EXPECT_EQ(registry.FindByHandle(secondHandle), &secondTwin) << kindName; + + second.reset(); + EXPECT_EQ(registry.FindByHandle(secondHandle), nullptr) << kindName; + } +} // namespace + +// The gate on MAJOR 2 of the round-3 review: every kind this package re-keyed, exercised +// through the registry the shipping code calls, on the arm this package wrote. +TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleArm) { + using namespace MobileGL; + using namespace MobileGL::MG_Backend::DirectGLES; + using namespace MobileGL::MG_State::GLState; + + if (!EsprytSlotTablesEnabled()) { + GTEST_SKIP() << "the legacy arm keys twins on the frontend heap address and answers the " + "null handle, so there is no {slot, gen} walk to drive"; + } + + ExpectTheHandleArmDrivesThisKind("Texture", TextureImpl::g_backendTextureObjects, [] { + return SharedPtr(MakeShared(0u)); + }); + ExpectTheHandleArmDrivesThisKind("Framebuffer", FramebufferImpl::g_backendFramebufferObjects, + [] { return MakeShared(1u); }); + ExpectTheHandleArmDrivesThisKind("Renderbuffer", RenderbufferImpl::g_backendRenderbufferObjects, + [] { return MakeShared(0u); }); + ExpectTheHandleArmDrivesThisKind("SamplerCso", SamplerImpl::g_backendSamplerObjects, + [] { return MakeShared(0u); }); + ExpectTheHandleArmDrivesThisKind("ShaderCso", PrgramImpl::g_backendProgramObjects, + [] { return MakeShared(0u); }); + ExpectTheHandleArmDrivesThisKind("VertexElementsCso", VertexArrayImpl::g_backendVertexArrayObjects, + [] { return MakeShared(0u); }); +} + // The gate on MAJOR 1 of the round-3 review. Commit d89fb684 raised // Fatal{PipeLegacyMemosDisabled} from inside InitDisplayAndContext(), i.e. from inside EGL // bring-up - and the integration harness pre-flights EGL bring-up in a FORKED CHILD, converting @@ -3679,4 +3773,8 @@ TEST(DirectGLESSlotTable, TheHandleArmInstallsTheDeathNoticeConsumer) { TEST(DirectGLESSlotTable, AnArmlessKnobCombinationStopsInsteadOfSkippingTheLane) { GTEST_SKIP() << "the {slot, gen} twin table is compiled only under MOBILEGL_PIPE_PUSH"; } + +TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleArm) { + GTEST_SKIP() << "the {slot, gen} twin table is compiled only under MOBILEGL_PIPE_PUSH"; +} #endif // MOBILEGL_PIPE_PUSH