[Test] (MG_Test, Pipe): pin the applier's own clear on the path a spawned server takes, and four named fields the stamp case must not re-derive from the table it is testing

This commit is contained in:
2026-09-11 14:53:39 -04:00
parent 3cb2c4aa9a
commit 126bd9aa62
+56 -4
View File
@@ -163,7 +163,7 @@ TEST(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) {
TEST(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) { TEST(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) {
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)"; GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
} }
TEST(FieldOwnershipTest, VerbBoundaryOpsAreTheFourTheStampRuleNames) { TEST(FieldOwnershipTest, VerbBoundaryOpsCoverEveryVerbShapedCall) {
GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)"; GTEST_SKIP() << "push not compiled in (MOBILEGL_PIPE_PUSH=OFF)";
} }
@@ -274,18 +274,35 @@ TEST_F(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) {
} }
} }
// The stamp map, and the one slot of class B that is deliberately NOT in it. // The stamp map, in both directions: the four P5 class-B boundaries, the eight mapped ahead of
TEST_F(FieldOwnershipTest, VerbBoundaryOpsAreTheFourTheStampRuleNames) { // the phase that will emit them, and the three verb-shaped calls that are exempt by name.
TEST_F(FieldOwnershipTest, VerbBoundaryOpsCoverEveryVerbShapedCall) {
// CONTRACT §7 class B minus Present - the only four that can arrive in P5.
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Clear), MGPipeVerb::Clear); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Clear), MGPipeVerb::Clear);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::DrawVbo), MGPipeVerb::DrawArrays); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::DrawVbo), MGPipeVerb::DrawArrays);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::ReadPixels), MGPipeVerb::ReadPixels); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::ReadPixels), MGPipeVerb::ReadPixels);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Blit), MGPipeVerb::BlitFramebuffer); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Blit), MGPipeVerb::BlitFramebuffer);
EXPECT_EQ(kMGPipeVerbBoundaryOpCount, SizeT{4}); // Class C today, mapped anyway: an OMITTED stamp row is silent, because the record would
// apply under the previous verb's serial, mask and name.
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::LaunchGrid), MGPipeVerb::DispatchCompute);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::MemoryBarrier), MGPipeVerb::MemoryBarrier);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::BeginStreamOutput), MGPipeVerb::BeginTransformFeedback);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::EndStreamOutput), MGPipeVerb::EndTransformFeedback);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::PauseStreamOutput), MGPipeVerb::PauseTransformFeedback);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::ResumeStreamOutput), MGPipeVerb::ResumeTransformFeedback);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::GenerateMipmap), MGPipeVerb::GenerateMipmap);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::GetTextureImage), MGPipeVerb::GetTextureImage);
EXPECT_EQ(kMGPipeVerbBoundaryOpCount, SizeT{12});
EXPECT_EQ(kMGPipeVerbBoundaryExemptCount, SizeT{3});
// Present is class B (it is emitted in P5) and is STILL not a verb boundary: // Present is class B (it is emitted in P5) and is STILL not a verb boundary:
// FillPoints.def:21 - "Present and SetSwapInterval go through BackendObject virtuals and // FillPoints.def:21 - "Present and SetSwapInterval go through BackendObject virtuals and
// read no frontend state, so they are not verbs here". Stamping there would retire the // read no frontend state, so they are not verbs here". Stamping there would retire the
// previous verb's answers with nothing to put in their place. // previous verb's answers with nothing to put in their place.
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Present), MGPipeVerb::kVerbCount); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Present), MGPipeVerb::kVerbCount);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetSwapInterval), MGPipeVerb::kVerbCount);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::Flush), MGPipeVerb::kVerbCount);
// ... and a record that is part of a verb rather than a boundary of one.
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetDynamicState), MGPipeVerb::kVerbCount); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetDynamicState), MGPipeVerb::kVerbCount);
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::GetCaps), MGPipeVerb::kVerbCount); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::GetCaps), MGPipeVerb::kVerbCount);
} }
@@ -320,6 +337,16 @@ TEST_F(FieldOwnershipTest, AServerStampMakesRecordSuppliedFieldsFreshAndWithdraw
} }
// Not vacuous: kClear really does have record-supplied fields to stamp. // Not vacuous: kClear really does have record-supplied fields to stamp.
EXPECT_GT(stamped, SizeT{0}); EXPECT_GT(stamped, SizeT{0});
// AND FOUR NAMED FIELDS, NOT DERIVED FROM THE ARRAY UNDER TEST. The loop above recomputes
// `answerable` out of kMGPipeFieldOwnership, so it can only catch a stamp that disagrees
// with the table - never a table that is wrong. These four say what the stamp must do for
// four fields whose class is an argument of this package rather than a lookup.
const MGPipeFilledState& filled = gPipeInputs.FilledState();
EXPECT_TRUE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetClearColor)); // supplied
EXPECT_TRUE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetRenderStateParameters));
EXPECT_FALSE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetFramebufferBindingSlot)); // pulled
EXPECT_FALSE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::RecordError)); // sticky
} }
// THE STICKY EXEMPTION, CANCELLED. generated/PipeFilled.inc answers "fresh" for a sticky field // THE STICKY EXEMPTION, CANCELLED. generated/PipeFilled.inc answers "fresh" for a sticky field
@@ -385,6 +412,24 @@ TEST_F(FieldOwnershipTest, NothingIsCountedOutsideAServerStampedVerb) {
EXPECT_FALSE(gPipeInputs.ServerStampedVerb()); EXPECT_FALSE(gPipeInputs.ServerStampedVerb());
} }
// THE APPLIER'S OWN CLEAR, reached directly rather than through the client's fill. In a spawned
// server MG_Impl is not in the process, so MGPipeValidateForVerb/MGPipeLeaveVerb never run and
// MGPipeServerClearVerbBoundary is the ONLY thing that can disarm the flag; without it the
// server latches TRUE after its first stamp and the sticky exemption - the one
// InvalidateCompileEnv is reached from backend initialisation under - is gone for good.
TEST_F(FieldOwnershipTest, TheAppliersOwnClearDisarmsTheStampWithoutTheClientsFill) {
MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels);
ASSERT_TRUE(gPipeInputs.ServerStampedVerb());
(void)gPipeInputs.ValidateProgramName(1u);
ASSERT_EQ(MGPipeResidualPullCount(), Uint64{1});
MGPipeServerClearVerbBoundary(); // what PipeApplier must call on leaving the applier
EXPECT_FALSE(gPipeInputs.ServerStampedVerb());
(void)gPipeInputs.ValidateProgramName(1u);
gPipeInputs.InvalidateCompileEnv();
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{1}) << "a forward outside a stamped verb was counted";
}
// The verb's own may-read table still holds on the server: kClear does not read // The verb's own may-read table still holds on the server: kClear does not read
// GetActiveTextureUnit, so reading it there is a stale answer rather than a residual pull, // GetActiveTextureUnit, so reading it there is a stale answer rather than a residual pull,
// and it stays Fatal. Counting it would trade a loud staleness for a quiet one. // and it stays Fatal. Counting it would trade a loud staleness for a quiet one.
@@ -484,6 +529,13 @@ TEST_F(FieldOwnershipTest, TheUnpackHalfOfThePixelStoreAbortsWhileThePackHalfDoe
EXPECT_NE(fatal.Log.find("Fatal{UnmigratedPipeInput, \"GetPixelStoreParameters@ReadPixels\"}"), EXPECT_NE(fatal.Log.find("Fatal{UnmigratedPipeInput, \"GetPixelStoreParameters@ReadPixels\"}"),
std::string::npos) std::string::npos)
<< fatal.Log; << fatal.Log;
// The line must say WHICH HALF. Without this the message is byte-identical to what a
// genuinely stale read of the pack half would print, and the whole case for narrowing by
// argument instead of by a second field id is that the reader is told which half they
// asked for.
EXPECT_NE(fatal.Log.find("argument 0 = 1 is FATAL while the field is APPLIER-DERIVED"),
std::string::npos)
<< fatal.Log;
const ChildResult ok = RunInChild([] { const ChildResult ok = RunInChild([] {
MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels); MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels);