mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-17 00:28:31 +09:00
Merge branch 'feat/disaggregated' into p5/v1
This commit is contained in:
@@ -43,6 +43,16 @@
|
|||||||
// address really is valid in this process, which is precisely why E3(d) is worth gating at all -
|
// address really is valid in this process, which is precisely why E3(d) is worth gating at all -
|
||||||
// and a direct count of declines would need a counter from packages b1/v1.
|
// and a direct count of declines would need a counter from packages b1/v1.
|
||||||
//
|
//
|
||||||
|
// AND MEMBERSHIP FOLLOWS THE ARM (ID-42). The client's live-persistent-map set is the set of maps
|
||||||
|
// the client still has to PUSH, so an ADOPTED store is deliberately not in it -
|
||||||
|
// PersistentMapTracker::IsLivePersistentMap's second row is IsBackendPersistentMapped(), and an
|
||||||
|
// adopted store's bytes are already in coherent GPU memory. AssertMembership therefore expects
|
||||||
|
// `live == (arm == emulated)`, from the same peek AssertOrRecordArm reads. The arm-INDEPENDENT
|
||||||
|
// claim - "the predicate reads the chain, not the adopt tier" - belongs to a unit case that can
|
||||||
|
// drive both answers on demand (SplitBufferSet.
|
||||||
|
// TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) and not to an integration entry, which
|
||||||
|
// only ever sees whichever arm its driver and transport happen to give it.
|
||||||
|
//
|
||||||
// `mpr` (map-persistent-roundtrips) is counted per ACQUISITION ATTEMPT, mint or decline
|
// `mpr` (map-persistent-roundtrips) is counted per ACQUISITION ATTEMPT, mint or decline
|
||||||
// (ARCHITECTURE.md:492), so it is the SAME NUMBER on both arms and in both transports: that is
|
// (ARCHITECTURE.md:492), so it is the SAME NUMBER on both arms and in both transports: that is
|
||||||
// what makes exit gate E3(c)'s "mpr equal to the monolith arm's" checkable by one process. Both
|
// what makes exit gate E3(c)'s "mpr equal to the monolith arm's" checkable by one process. Both
|
||||||
@@ -243,6 +253,11 @@ void main() { oColor = vec4(vColor, 1.0); }
|
|||||||
ASSERT_TRUE(PeekBufferIsAdoptedPersistentMap(vbo, &adopted))
|
ASSERT_TRUE(PeekBufferIsAdoptedPersistentMap(vbo, &adopted))
|
||||||
<< "no frontend BufferObject behind GL buffer " << vbo << " " << when;
|
<< "no frontend BufferObject behind GL buffer " << vbo << " " << when;
|
||||||
const char* arm = adopted ? "adopted" : "emulated";
|
const char* arm = adopted ? "adopted" : "emulated";
|
||||||
|
// REMEMBERED, because AssertMembership's expectation is a function of it (ID-42)
|
||||||
|
// and the two have to be talking about ONE observation of ONE buffer. A second
|
||||||
|
// peek would be a second question, and a scenario that asked it twice could
|
||||||
|
// straddle a change and then compare two different moments.
|
||||||
|
m_observedArm = adopted ? ObservedArm::Adopted : ObservedArm::Emulated;
|
||||||
RecordProperty("persistent_map_arm", arm);
|
RecordProperty("persistent_map_arm", arm);
|
||||||
if (declared.empty()) return;
|
if (declared.empty()) return;
|
||||||
ASSERT_EQ(declared, std::string(arm))
|
ASSERT_EQ(declared, std::string(arm))
|
||||||
@@ -259,21 +274,63 @@ void main() { oColor = vec4(vColor, 1.0); }
|
|||||||
// b1-v1.md 4.1 item 2: the client's live-persistent-map set is meant to be exactly
|
// b1-v1.md 4.1 item 2: the client's live-persistent-map set is meant to be exactly
|
||||||
// SyncPersistentMappedRange's early-out chain. A drift between the two stops the push
|
// SyncPersistentMappedRange's early-out chain. A drift between the two stops the push
|
||||||
// silently; asking here makes it a named failure instead.
|
// silently; asking here makes it a named failure instead.
|
||||||
|
//
|
||||||
|
// MEMBERSHIP IS A PROPERTY OF THE ARM, NOT OF THE FLAGS - INTEGRATOR DECISION ID-42,
|
||||||
|
// and the first cut of this function got it wrong in a way no lane could see. It
|
||||||
|
// asserted `live` UNCONDITIONALLY, on the grounds that "a PERSISTENT|WRITE|COHERENT map
|
||||||
|
// that is not FLUSH_EXPLICIT is a member by construction". That sentence is true only
|
||||||
|
// on the EMULATED arm. PersistentMapTracker::IsLivePersistentMap's second row is
|
||||||
|
// `if (buffer.IsBackendPersistentMapped()) return false;` - deliberately, because the
|
||||||
|
// predicate must read the CHAIN and not the tier: an adopted store's bytes are already
|
||||||
|
// in coherent GPU memory and there is nothing for the push to ship, so it is not a
|
||||||
|
// member and must not be one. On the adopted arm `live` is false BY DESIGN.
|
||||||
|
//
|
||||||
|
// It survived the first wave because the two builds that ran it could not contradict
|
||||||
|
// it: the push build compiles no MG_Remote, so MGITEST_PERSISTENT_MAP_TRACKER is
|
||||||
|
// undefined and this function returned at the line above before asserting anything;
|
||||||
|
// the split build's monolith lane, where the tracker IS compiled, adopts
|
||||||
|
// (MGPipeApplyMapPersistent's R-6 decline is ANDed with `Transport != Monolith`,
|
||||||
|
// PipeApply.cpp:2005), and seven entries went red the first time the assertion ran at
|
||||||
|
// all.
|
||||||
|
//
|
||||||
|
// So the expectation is `live == (arm == emulated)`, taken from the SAME peek
|
||||||
|
// AssertOrRecordArm read. The arm-independent half - "the predicate reads the chain,
|
||||||
|
// not the tier" - is not an integration claim and is pinned where it can be driven
|
||||||
|
// directly, by MG_Test/Buffer/SplitBufferTest.cpp's
|
||||||
|
// SplitBufferSet.TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo.
|
||||||
void AssertMembership(unsigned int vbo, const char* when) {
|
void AssertMembership(unsigned int vbo, const char* when) {
|
||||||
if (!PersistentMapTrackerAvailable()) {
|
if (!PersistentMapTrackerAvailable()) {
|
||||||
RecordProperty("persistent_map_membership", "unavailable");
|
RecordProperty("persistent_map_membership", "unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (m_observedArm == ObservedArm::NotLookedAt) {
|
||||||
|
// AssertOrRecordArm could not look, so there is no arm to condition on and
|
||||||
|
// "could not look" is not "it was emulated" (PersistentMapPeek.h). A lane that
|
||||||
|
// DECLARES an arm has already been skipped by AssertOrRecordArm in this case.
|
||||||
|
RecordProperty("persistent_map_membership", "arm unknown");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const bool emulated = (m_observedArm == ObservedArm::Emulated);
|
||||||
bool live = false;
|
bool live = false;
|
||||||
ASSERT_TRUE(PeekBufferIsLivePersistentMap(vbo, &live))
|
ASSERT_TRUE(PeekBufferIsLivePersistentMap(vbo, &live))
|
||||||
<< "the tracker is compiled in but could not answer for GL buffer " << vbo << " " << when;
|
<< "the tracker is compiled in but could not answer for GL buffer " << vbo << " " << when;
|
||||||
EXPECT_TRUE(live)
|
RecordProperty("persistent_map_membership", live ? "member" : "not a member");
|
||||||
<< "a PERSISTENT|WRITE|COHERENT map that is not FLUSH_EXPLICIT is a member of "
|
EXPECT_EQ(live, emulated)
|
||||||
"the client's live-persistent-map set by construction, and it is not one "
|
<< "the client's live-persistent-map set is the set of maps the client still has "
|
||||||
<< when
|
"to PUSH, so membership follows the arm: on the emulated arm this "
|
||||||
|
"PERSISTENT|WRITE|COHERENT non-FLUSH_EXPLICIT map must be a member, and on "
|
||||||
|
"the adopted arm it must not be - IsLivePersistentMap's "
|
||||||
|
"IsBackendPersistentMapped() row takes it out, because an adopted store's "
|
||||||
|
"bytes are already in coherent GPU memory and there is nothing to ship. This "
|
||||||
|
"map landed in the "
|
||||||
|
<< (emulated ? "emulated" : "adopted") << " arm " << when << " and the predicate "
|
||||||
|
<< (live ? "made it a member" : "did not make it a member")
|
||||||
<< ". The set is supposed to BE SyncPersistentMappedRange's early-out chain; if "
|
<< ". The set is supposed to BE SyncPersistentMappedRange's early-out chain; if "
|
||||||
"they have drifted, the push stops shipping this buffer's blocks and nothing "
|
"they have drifted, the push either stops shipping this buffer's blocks or "
|
||||||
"else says so.";
|
"starts shipping an adopted store's, and nothing else says so. (ID-42. The "
|
||||||
|
"arm-independent statement - that the predicate reads the chain and not the "
|
||||||
|
"adopt tier - is pinned by SplitBufferSet."
|
||||||
|
"TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo, not here.)";
|
||||||
}
|
}
|
||||||
|
|
||||||
void TearDown() override {
|
void TearDown() override {
|
||||||
@@ -320,6 +377,13 @@ void main() { oColor = vec4(vColor, 1.0); }
|
|||||||
return RegionIsMostly(image, 8, image.Width() - 9, 8, image.Height() - 9, color, 0.0, when);
|
return RegionIsMostly(image, 8, image.Width() - 9, 8, image.Height() - 9, color, 0.0, when);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The arm this scenario OBSERVED, as opposed to the one its lane declared. A lane may
|
||||||
|
// declare none (the monolith counting lane does not), and the peek may be unable to
|
||||||
|
// look at all, so the third state is not "assume emulated" - it is "there is no arm to
|
||||||
|
// condition on", and AssertMembership records rather than asserts under it.
|
||||||
|
enum class ObservedArm { NotLookedAt, Adopted, Emulated };
|
||||||
|
ObservedArm m_observedArm = ObservedArm::NotLookedAt;
|
||||||
|
|
||||||
unsigned int m_program = 0;
|
unsigned int m_program = 0;
|
||||||
unsigned int m_vao = 0;
|
unsigned int m_vao = 0;
|
||||||
unsigned int m_vbo = 0;
|
unsigned int m_vbo = 0;
|
||||||
|
|||||||
@@ -41,6 +41,21 @@ namespace {
|
|||||||
using MG_Remote::Client::GpuWriteProducer;
|
using MG_Remote::Client::GpuWriteProducer;
|
||||||
using MG_Remote::Client::PersistentMapTracker;
|
using MG_Remote::Client::PersistentMapTracker;
|
||||||
|
|
||||||
|
// A backend that MINTS a persistent mapping, for the one case that needs the adopted arm
|
||||||
|
// (TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo). Only AcquirePersistentMap is
|
||||||
|
// filled in: the frontend null-checks every op individually, and a table with one live
|
||||||
|
// member is the smallest thing that makes AcquireMemoryRange's legacy adoption arm fire.
|
||||||
|
// The storage is the CASE's, not this table's - AdoptPersistentMap keeps the pointer and
|
||||||
|
// never owns it - so the base travels through a file-scope variable the case sets and
|
||||||
|
// clears around the one acquisition it wants minted.
|
||||||
|
void* g_mintedPersistentBase = nullptr;
|
||||||
|
void* MintPersistentMap(BufferObject&) { return g_mintedPersistentBase; }
|
||||||
|
const MG_State::GLState::BufferBackendOps g_mintingBufferOps = [] {
|
||||||
|
MG_State::GLState::BufferBackendOps ops{};
|
||||||
|
ops.AcquirePersistentMap = &MintPersistentMap;
|
||||||
|
return ops;
|
||||||
|
}();
|
||||||
|
|
||||||
// Everything in this package is gated on `Transport != Monolith`, so every case has to
|
// Everything in this package is gated on `Transport != Monolith`, so every case has to
|
||||||
// put the process into a split configuration and put it back. A fixture rather than a
|
// put the process into a split configuration and put it back. A fixture rather than a
|
||||||
// lambda because the tracker is a process-wide singleton and a case that left an entry in
|
// lambda because the tracker is a process-wide singleton and a case that left an entry in
|
||||||
@@ -64,6 +79,11 @@ namespace {
|
|||||||
MG_Remote::Client::ResetProducerMarkCountsForTest();
|
MG_Remote::Client::ResetProducerMarkCountsForTest();
|
||||||
}
|
}
|
||||||
void TearDown() override {
|
void TearDown() override {
|
||||||
|
// Belt and braces for the one case that installs a minting backend: a table left
|
||||||
|
// behind would make the NEXT case's acquisition land in the adopted arm, and every
|
||||||
|
// membership assertion in this file would then be about a different code path.
|
||||||
|
MG_State::GLState::SetBufferBackendOps(nullptr);
|
||||||
|
g_mintedPersistentBase = nullptr;
|
||||||
PersistentMapTracker::Instance().ClearForTest();
|
PersistentMapTracker::Instance().ClearForTest();
|
||||||
MG_State::pGLContext = Move(m_context);
|
MG_State::pGLContext = Move(m_context);
|
||||||
MG_Config::Transport = m_transport;
|
MG_Config::Transport = m_transport;
|
||||||
@@ -258,6 +278,92 @@ TEST_F(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) {
|
|||||||
buffer->ReleaseMemory(false);
|
buffer->ReleaseMemory(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THE ADOPTED ARM IS NOT A MEMBER, AND IT IS THE CHAIN THAT SAYS SO - NOT THE TIER (ID-42).
|
||||||
|
//
|
||||||
|
// IsLivePersistentMap's second row is `if (buffer.IsBackendPersistentMapped()) return false;`,
|
||||||
|
// and its comment promises that the row answers for ITSELF: at tier T2 the arm is unreachable
|
||||||
|
// because MapPersistent declines, but a build that reaches T0/T1 later must get the same answer
|
||||||
|
// out of the same row. Nothing drove that promise. The case above only ever sees the declined
|
||||||
|
// arm, and PersistentCoherentMapScenario's membership assertion cannot pin it either - an
|
||||||
|
// integration entry sees whichever arm its driver and transport hand it, which is exactly how
|
||||||
|
// that assertion came to state the emulated arm's property as if it were universal and go red on
|
||||||
|
// seven split-monolith entries the first time it ran.
|
||||||
|
//
|
||||||
|
// So the statement is pinned HERE, where both answers can be produced on demand. The adoption is
|
||||||
|
// made by the PRODUCTION path - AcquireMemoryRange dispatching to the backend's
|
||||||
|
// AcquirePersistentMap and calling PipeResource::AdoptPersistentMap - and not by the case
|
||||||
|
// reaching into the buffer, because a hand-set flag would still be "true" with the production
|
||||||
|
// adoption deleted (R-16). The transport is Monolith for exactly that call, because R-6's decline
|
||||||
|
// is ANDed with `Transport != Monolith` and there is no other way to reach a mint in this build;
|
||||||
|
// the PREDICATE is then asked with the tier back at T2/InProcess, which is the whole point: the
|
||||||
|
// tier says "emulated, always" and the chain still says "not a member".
|
||||||
|
TEST_F(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) {
|
||||||
|
constexpr SizeT kSize = 4096;
|
||||||
|
// The storage the backend "mints". Declared first so it outlives the buffer: AdoptPersistentMap
|
||||||
|
// stores the pointer and releases the shadow, and it never owns what it was handed.
|
||||||
|
Vector<Uint8> minted(kSize, static_cast<Uint8>(0));
|
||||||
|
g_mintedPersistentBase = minted.data();
|
||||||
|
|
||||||
|
auto adopted = MakeBuffer(27u, kSize);
|
||||||
|
auto declined = MakeBuffer(28u, kSize);
|
||||||
|
|
||||||
|
// The declined twin first, with no backend ops at all: same flags, same size, same call.
|
||||||
|
declined->AcquireMemoryRange(Range1D{0, kSize},
|
||||||
|
BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent);
|
||||||
|
ASSERT_FALSE(declined->IsBackendPersistentMapped());
|
||||||
|
ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*declined))
|
||||||
|
<< "the emulated arm IS a member - if this fails the two halves are not comparable and the "
|
||||||
|
"assertion below proves nothing";
|
||||||
|
|
||||||
|
{
|
||||||
|
MG_State::GLState::SetBufferBackendOps(&g_mintingBufferOps);
|
||||||
|
MG_Config::Transport = MG_Config::TransportMode::Monolith;
|
||||||
|
adopted->AcquireMemoryRange(Range1D{0, kSize},
|
||||||
|
BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent);
|
||||||
|
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||||
|
MG_State::GLState::SetBufferBackendOps(nullptr);
|
||||||
|
}
|
||||||
|
ASSERT_TRUE(adopted->IsBackendPersistentMapped())
|
||||||
|
<< "the backend declined the mint, so there is no adopted arm here to ask about";
|
||||||
|
|
||||||
|
// THE TIER SAYS EMULATED. The chain must still say "not a member".
|
||||||
|
ASSERT_TRUE(MG_Remote::Client::AdoptTierIsEmulate());
|
||||||
|
ASSERT_NE(MG_Config::Transport, MG_Config::TransportMode::Monolith);
|
||||||
|
EXPECT_FALSE(PersistentMapTracker::IsLivePersistentMap(*adopted))
|
||||||
|
<< "an adopted store's bytes are already in host-visible coherent GPU memory and there is "
|
||||||
|
"nothing for the push to ship, so IsBackendPersistentMapped() takes it out of the set. "
|
||||||
|
"This answer must come from that ROW and not from the adopt tier: the tier is T2 and the "
|
||||||
|
"transport is InProcess right now, which is the configuration in which R-6 says every "
|
||||||
|
"acquisition declines - and the store in front of the predicate is adopted anyway, "
|
||||||
|
"because a later phase's T0/T1 will mint one. A predicate that read the tier would call "
|
||||||
|
"it a member and the push would read an adopted store's Bytes() as if it were the "
|
||||||
|
"shadow.";
|
||||||
|
|
||||||
|
// The SET agrees with the predicate, asked through the production entry point rather than by
|
||||||
|
// reading a member: NoteMapStateChanged is what every one of the five maintenance events
|
||||||
|
// calls, and it must refuse to enrol an adopted store.
|
||||||
|
PersistentMapTracker::Instance().NoteMapStateChanged(*adopted);
|
||||||
|
EXPECT_EQ(PersistentMapTracker::Instance().MemberCount(), 1u)
|
||||||
|
<< "only the declined twin: enrolling an adopted store would make the push read its "
|
||||||
|
"Bytes() - which is the GPU map, not the shadow - as if it were bytes to ship";
|
||||||
|
|
||||||
|
// ...and the consequence, which is the one that would actually corrupt something.
|
||||||
|
const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush);
|
||||||
|
MG_Remote::Client::PushPersistentMapsBeforeVerb();
|
||||||
|
EXPECT_EQ(MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush) - before,
|
||||||
|
static_cast<Uint64>(kSize))
|
||||||
|
<< "the declined twin's 4096 bytes and nothing else: the adopted buffer must contribute no "
|
||||||
|
"pushed bytes at all";
|
||||||
|
|
||||||
|
declined->ReleaseMemory(false);
|
||||||
|
// The adopted one is NOT released through ReleaseMemory: ReleasePersistentMap is for a store
|
||||||
|
// being redefined, and a persistent map the application holds outlives every unmap by
|
||||||
|
// definition (PipeResource.h:121-127). It is dropped here with the mapping still adopted,
|
||||||
|
// which is also the shape ~BufferObject has to survive.
|
||||||
|
adopted.reset();
|
||||||
|
g_mintedPersistentBase = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// pmap is non-zero, and it is non-zero in BLOCKS.
|
// pmap is non-zero, and it is non-zero in BLOCKS.
|
||||||
TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) {
|
TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) {
|
||||||
constexpr SizeT kSize = 4u * 64u * 1024u; // exactly four 64 KiB blocks
|
constexpr SizeT kSize = 4u * 64u * 1024u; // exactly four 64 KiB blocks
|
||||||
@@ -402,7 +508,7 @@ TEST_F(SplitBufferSet, OnlyAdoptTierTwoIsImplemented) {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// THE SAME FIFTEEN NAMES, SO THE ctest NAME SET DOES NOT MOVE BETWEEN LANES. G2 compares the
|
// THE SAME SIXTEEN NAMES, SO THE ctest NAME SET DOES NOT MOVE BETWEEN LANES. G2 compares the
|
||||||
// pull and push name lists line for line and G14 allows build-split to ADD names but never to
|
// pull and push name lists line for line and G14 allows build-split to ADD names but never to
|
||||||
// remove one, so a case that exists only where it can run would break both gates for a reason
|
// remove one, so a case that exists only where it can run would break both gates for a reason
|
||||||
// that has nothing to do with what it tests. It skips instead, and says why.
|
// that has nothing to do with what it tests. It skips instead, and says why.
|
||||||
@@ -417,6 +523,7 @@ TEST(SplitBufferSet, Row4AReadPixelsIntoAPackPboMarksThePbo) { MGL_SPLIT_ONLY_OR
|
|||||||
TEST(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
TEST(SplitBufferSet, TheWholeSetIsInertOnTheMonolithPath) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, TheWholeSetIsInertOnTheMonolithPath) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
TEST(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
|
TEST(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
TEST(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
TEST(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
TEST(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
TEST(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { MGL_SPLIT_ONLY_OR_SKIP(); }
|
||||||
|
|||||||
@@ -702,9 +702,11 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) {
|
|||||||
static_assert(Call(kHasBlob) == Rec(kRecHasBlob), "bit 1 stopped agreeing");
|
static_assert(Call(kHasBlob) == Rec(kRecHasBlob), "bit 1 stopped agreeing");
|
||||||
|
|
||||||
// The three that collide. Each of these is a live defect if it is ever passed through,
|
// The three that collide. Each of these is a live defect if it is ever passed through,
|
||||||
// and the middle one is the worst: RingConsumer::Pop treats kRecPad as a wrap filler and
|
// and the middle one was the worst: kRecPad is the flag RingConsumer::Pop reads as "wrap
|
||||||
// SKIPS the record, so a stamped kVarTail deletes every variable-tail call from the
|
// filler", so on the flag alone a stamped kVarTail would delete every variable-tail call
|
||||||
// stream with no error raised anywhere.
|
// from the stream with no error raised anywhere. Pop does not read it on the flag alone -
|
||||||
|
// it requires kind == kRingPadRecordKind beside it - and the runtime half below is the
|
||||||
|
// control on exactly that.
|
||||||
static_assert(Call(kVarTail) == Rec(kRecPad), "the kVarTail/kRecPad collision moved");
|
static_assert(Call(kVarTail) == Rec(kRecPad), "the kVarTail/kRecPad collision moved");
|
||||||
static_assert(Call(kHostSpan) == Rec(kRecBorrowSlot), "the kHostSpan/kRecBorrowSlot collision moved");
|
static_assert(Call(kHostSpan) == Rec(kRecBorrowSlot), "the kHostSpan/kRecBorrowSlot collision moved");
|
||||||
static_assert(Call(kReplySlot) == Rec(kRecVarTail), "the kReplySlot/kRecVarTail collision moved");
|
static_assert(Call(kReplySlot) == Rec(kRecVarTail), "the kReplySlot/kRecVarTail collision moved");
|
||||||
@@ -757,8 +759,17 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) {
|
|||||||
// The one that Reserve cannot defend: a producer that writes the header ITSELF rather than
|
// The one that Reserve cannot defend: a producer that writes the header ITSELF rather than
|
||||||
// letting Reserve write it - which is precisely what a codec with its own header struct
|
// letting Reserve write it - which is precisely what a codec with its own header struct
|
||||||
// does, since MGPWireRecHeader and RingRecordHeader are the same eight bytes. Then the
|
// does, since MGPWireRecHeader and RingRecordHeader are the same eight bytes. Then the
|
||||||
// mask is not in the path, Pop sees kRecPad, and the record is skipped as a wrap filler
|
// mask is not in the path and Pop sees kRecPad on a record that is not a filler.
|
||||||
// with no error raised anywhere.
|
//
|
||||||
|
// What saves it is the KIND. RingConsumer::Pop skips a record only when it carries kRecPad
|
||||||
|
// AND kind == kRingPadRecordKind (Ring.cpp: `(header.flags & kRecPad) != 0 && header.kind ==
|
||||||
|
// kRingPadRecordKind` - "BOTH, not just the flag"). Kind 0 is the wrap filler's and nothing
|
||||||
|
// else's, because the call catalogue starts at 1. So the stamped record is DELIVERED, lies
|
||||||
|
// and all, and the decoder can reject it by name - which it can only do because it got it.
|
||||||
|
// THE ASSERT AND THE EXPECTS BELOW ARE THE CONTROL ON THAT KIND CHECK: delete the
|
||||||
|
// `&& header.kind == kRingPadRecordKind` half of Pop's condition and this record vanishes
|
||||||
|
// into the wrap-filler skip again, exactly as it did before the pair was required, and this
|
||||||
|
// case goes red on the ASSERT's own message. That perturbation was run.
|
||||||
void* second = ring.Producer().Reserve(static_cast<std::uint16_t>(MGPWireOp::DrawVbo),
|
void* second = ring.Producer().Reserve(static_cast<std::uint16_t>(MGPWireOp::DrawVbo),
|
||||||
kRecNone, 16);
|
kRecNone, 16);
|
||||||
ASSERT_NE(second, nullptr);
|
ASSERT_NE(second, nullptr);
|
||||||
@@ -771,10 +782,51 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) {
|
|||||||
sizeof(stamped));
|
sizeof(stamped));
|
||||||
ring.Producer().Publish();
|
ring.Producer().Publish();
|
||||||
|
|
||||||
|
ASSERT_TRUE(ring.Consumer().Pop(view))
|
||||||
|
<< "the stamped record vanished into Pop's wrap-filler skip. Pop's kind check - a filler "
|
||||||
|
"must carry kind == kRingPadRecordKind as well as kRecPad - is the only thing standing "
|
||||||
|
"between a header stamped with the CALL flags and every var-tail call being deleted "
|
||||||
|
"from the stream with nothing logged on either side";
|
||||||
|
EXPECT_EQ(view.kind, static_cast<std::uint16_t>(MGPWireOp::DrawVbo))
|
||||||
|
<< "kind is what Pop tells a real record from a filler by, and a filler's is "
|
||||||
|
"kRingPadRecordKind";
|
||||||
|
|
||||||
|
// Delivered is not the same as correct. The stamped bits arrive verbatim, and they are
|
||||||
|
// exactly the collisions the table above names: bit 2 (kVarTail -> kRecPad) is why this
|
||||||
|
// record looked like a filler at all, and bit 3 (kHostSpan -> kRecBorrowSlot) still makes it
|
||||||
|
// claim a slot in the GPU timeline it never borrowed. draw_vbo is not a kReplySlot call, so
|
||||||
|
// bit 4 (kReplySlot -> kRecVarTail) is clear here; on a blocking call it would lie too.
|
||||||
|
EXPECT_EQ(view.flags, static_cast<std::uint16_t>(drawVboCallFlags))
|
||||||
|
<< "the header did not arrive as it was stamped";
|
||||||
|
EXPECT_NE(view.flags & static_cast<std::uint16_t>(kRecPad), 0u)
|
||||||
|
<< "the kVarTail/kRecPad collision arrives intact - the kind check narrows the SKIP, it "
|
||||||
|
"does not scrub the bit, and naming this record is the decoder's job";
|
||||||
|
EXPECT_NE(view.flags & static_cast<std::uint16_t>(kRecBorrowSlot), 0u)
|
||||||
|
<< "the kHostSpan/kRecBorrowSlot collision is what makes a stamped header lie about "
|
||||||
|
"this record's lifetime";
|
||||||
|
EXPECT_EQ(view.flags & static_cast<std::uint16_t>(kRecVarTail), 0u)
|
||||||
|
<< "draw_vbo started carrying kReplySlot; then the third collision lies here too";
|
||||||
|
EXPECT_EQ(view.payloadSize, 16u);
|
||||||
|
EXPECT_TRUE(ring.Invariants());
|
||||||
|
|
||||||
|
// The other side of the same control, so that "the record is popped" cannot be satisfied by
|
||||||
|
// simply not skipping anything: kRecPad on a header whose kind IS kRingPadRecordKind is a
|
||||||
|
// genuine wrap filler and still vanishes. Pop's check was narrowed to the pair, not removed.
|
||||||
|
void* filler = ring.Producer().Reserve(kRingPadRecordKind, kRecNone, 16);
|
||||||
|
ASSERT_NE(filler, nullptr);
|
||||||
|
std::memset(filler, 0xEF, 16);
|
||||||
|
RingRecordHeader asFiller{};
|
||||||
|
std::memcpy(&asFiller, static_cast<std::uint8_t*>(filler) - sizeof(RingRecordHeader),
|
||||||
|
sizeof(asFiller));
|
||||||
|
asFiller.flags = static_cast<std::uint16_t>(kRecPad);
|
||||||
|
std::memcpy(static_cast<std::uint8_t*>(filler) - sizeof(RingRecordHeader), &asFiller,
|
||||||
|
sizeof(asFiller));
|
||||||
|
ring.Producer().Publish();
|
||||||
|
|
||||||
EXPECT_FALSE(ring.Consumer().Pop(view))
|
EXPECT_FALSE(ring.Consumer().Pop(view))
|
||||||
<< "a header stamped with the CALL flags outside Reserve should vanish into Pop's "
|
<< "a header carrying BOTH kRecPad and kind kRingPadRecordKind is a wrap filler and has "
|
||||||
"wrap-filler skip - if it did not, the collision has moved and this case is no "
|
"to be skipped; if it reaches a caller the skip is gone, not narrowed";
|
||||||
"longer the control it was";
|
EXPECT_TRUE(ring.Invariants());
|
||||||
|
|
||||||
// And the same record framed the way the encoder actually frames it - translated, with the
|
// And the same record framed the way the encoder actually frames it - translated, with the
|
||||||
// ring's own var-tail bit - round-trips intact.
|
// ring's own var-tail bit - round-trips intact.
|
||||||
|
|||||||
+70
-12
@@ -1376,27 +1376,54 @@ def write(path, text, check, changed):
|
|||||||
handle.write(text)
|
handle.write(text)
|
||||||
|
|
||||||
|
|
||||||
def expect_trip(name, fn):
|
def expect_trip(name, because, fn, quiet=False):
|
||||||
"""Runs one negative control; a gate that lets it through is the failure."""
|
"""Runs one negative control; a gate that lets it through, or trips for a reason that is
|
||||||
|
not ITS OWN, is the failure.
|
||||||
|
|
||||||
|
The first version of this function caught any SystemExit - including an unrelated
|
||||||
|
diagnostic, and even a CLEAN sys.exit(0) - and asked nothing about which one, so the
|
||||||
|
codex cross-family review's finding 9 could replace a control's callback with either and
|
||||||
|
the "nine negative controls all trip" line stayed true while the guard it named never
|
||||||
|
fired. gen_pipe_field_ownership.py's M-1 was the same defect in the sibling generator;
|
||||||
|
this mirrors that fix.
|
||||||
|
|
||||||
|
`because` is a substring the control's OWN message must contain, and an exit code of 0
|
||||||
|
can never count as a trip."""
|
||||||
try:
|
try:
|
||||||
fn()
|
fn()
|
||||||
except SystemExit as trip:
|
except SystemExit as trip:
|
||||||
print("gen_pipe: self-test %s: tripped as expected (%s)" % (name, str(trip).splitlines()[0][:100]))
|
code = trip.code
|
||||||
return 1
|
message = str(code) if code is not None else ""
|
||||||
print("gen_pipe: self-test %s: DID NOT TRIP" % name, file=sys.stderr)
|
if code != 0 and because in message:
|
||||||
|
if not quiet:
|
||||||
|
print("gen_pipe: self-test %s: tripped as expected (%s)"
|
||||||
|
% (name, message.splitlines()[0][:100]))
|
||||||
|
return 1
|
||||||
|
if not quiet:
|
||||||
|
print("gen_pipe: self-test %s: tripped for SOMEONE ELSE'S reason:\n"
|
||||||
|
" expected to contain: %s\n"
|
||||||
|
" actually said (code=%r): %s"
|
||||||
|
% (name, because, code, message.splitlines()[0][:200] if message else "<empty>"),
|
||||||
|
file=sys.stderr)
|
||||||
|
return 0
|
||||||
|
if not quiet:
|
||||||
|
print("gen_pipe: self-test %s: DID NOT TRIP" % name, file=sys.stderr)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def self_test(accessors):
|
def self_test(accessors):
|
||||||
"""The negative controls (check_include_closure.py's shape): each gate must go red for
|
"""The negative controls (check_include_closure.py's shape): each gate must go red for
|
||||||
its reason, and zero trips is itself an error."""
|
its OWN reason, and zero trips is itself an error."""
|
||||||
canned_struct = "struct Canned {\n Uint32 A;\n Uint32 B, C;\n Uint8 Pad0[3];\n void F() { return; }\n};\n"
|
canned_struct = "struct Canned {\n Uint32 A;\n Uint32 B, C;\n Uint8 Pad0[3];\n void F() { return; }\n};\n"
|
||||||
controls = [
|
controls = [
|
||||||
("struct member without F(...)",
|
("struct member without F(...)",
|
||||||
|
"member(s) with no F(...) in PipeFields.def",
|
||||||
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B"]}, ["Canned"], [canned_struct])),
|
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B"]}, ["Canned"], [canned_struct])),
|
||||||
("F(...) that is not a member",
|
("F(...) that is not a member",
|
||||||
|
"F(...) name(s) that are not members",
|
||||||
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B", "C", "D"]}, ["Canned"], [canned_struct])),
|
lambda: check_field_lists_cover_struct_members({"Canned": ["A", "B", "C", "D"]}, ["Canned"], [canned_struct])),
|
||||||
("payload with no struct",
|
("payload with no struct",
|
||||||
|
"struct not found",
|
||||||
lambda: check_field_lists_cover_struct_members({"Nowhere": ["A"]}, ["Nowhere"], [canned_struct])),
|
lambda: check_field_lists_cover_struct_members({"Nowhere": ["A"]}, ["Nowhere"], [canned_struct])),
|
||||||
]
|
]
|
||||||
fill_text = read(os.path.join(PIPE_DIR, "FillPoints.def"))
|
fill_text = read(os.path.join(PIPE_DIR, "FillPoints.def"))
|
||||||
@@ -1404,16 +1431,24 @@ def self_test(accessors):
|
|||||||
field_row = re.compile(r"X\(\s*kDraw\s*,\s*GetBoundVertexArray\s*\)")
|
field_row = re.compile(r"X\(\s*kDraw\s*,\s*GetBoundVertexArray\s*\)")
|
||||||
if not verb_row.search(fill_text) or not field_row.search(fill_text):
|
if not verb_row.search(fill_text) or not field_row.search(fill_text):
|
||||||
sys.exit("gen_pipe: self-test: FillPoints.def lost the rows the controls edit")
|
sys.exit("gen_pipe: self-test: FillPoints.def lost the rows the controls edit")
|
||||||
controls.append(("verb missing from FillPoints.def", lambda: parse_fill_points(
|
controls.append(("verb missing from FillPoints.def",
|
||||||
|
"GLFunctionsTable member(s) without a verb row",
|
||||||
|
lambda: parse_fill_points(
|
||||||
accessors, text=verb_row.sub("", fill_text, count=1))))
|
accessors, text=verb_row.sub("", fill_text, count=1))))
|
||||||
controls.append(("verb that is not a GLFunctionsTable member", lambda: parse_fill_points(
|
controls.append(("verb that is not a GLFunctionsTable member",
|
||||||
|
"verb(s) that are not GLFunctionsTable members",
|
||||||
|
lambda: parse_fill_points(
|
||||||
accessors, text=verb_row.sub("X(DrawArrays, kDraw) X(NotAVerb, kDraw)", fill_text, count=1))))
|
accessors, text=verb_row.sub("X(DrawArrays, kDraw) X(NotAVerb, kDraw)", fill_text, count=1))))
|
||||||
controls.append(("field row naming a non-accessor", lambda: parse_fill_points(
|
controls.append(("field row naming a non-accessor",
|
||||||
|
"is not an accessor in Coverage.def",
|
||||||
|
lambda: parse_fill_points(
|
||||||
accessors, text=field_row.sub("X(kDraw, NotAnAccessor)", fill_text, count=1))))
|
accessors, text=field_row.sub("X(kDraw, NotAnAccessor)", fill_text, count=1))))
|
||||||
# The EMITTED list's own gate: a row naming a call that is not in PipeCalls.def would
|
# The EMITTED list's own gate: a row naming a call that is not in PipeCalls.def would
|
||||||
# generate an enumerator nothing can dispatch on.
|
# generate an enumerator nothing can dispatch on.
|
||||||
calls_for_control = parse_calls()
|
calls_for_control = parse_calls()
|
||||||
controls.append(("emitted row naming a call that does not exist", lambda: gen_emitted_by(
|
controls.append(("emitted row naming a call that does not exist",
|
||||||
|
"which is not a call in PipeCalls.def",
|
||||||
|
lambda: gen_emitted_by(
|
||||||
[("GetViewport", "SetDynamicState")], calls_for_control, [("GetViewport", "NotACall")])))
|
[("GetViewport", "SetDynamicState")], calls_for_control, [("GetViewport", "NotACall")])))
|
||||||
# P5 R-13.4's gate. The flags are now a GENERATED TABLE six packages read instead of six
|
# P5 R-13.4's gate. The flags are now a GENERATED TABLE six packages read instead of six
|
||||||
# hard-coded copies, so a token that is not an MGPipeCallFlags enumerator has to stop the
|
# hard-coded copies, so a token that is not an MGPipeCallFlags enumerator has to stop the
|
||||||
@@ -1421,13 +1456,36 @@ def self_test(accessors):
|
|||||||
# kNone, the empty set, may not be OR'd with a real flag and quietly read as one.
|
# kNone, the empty set, may not be OR'd with a real flag and quietly read as one.
|
||||||
flag_typo = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kHasBlobb"])
|
flag_typo = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kHasBlobb"])
|
||||||
flag_kNone = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kNone", "kHasBlob"])
|
flag_kNone = Call(1, "Canned", "MGPHandleOnly", "kScreen", ["kNone", "kHasBlob"])
|
||||||
|
flag_typo_because = "which is not an MGPipeCallFlags enumerator"
|
||||||
controls.append(("call flag that is not an MGPipeCallFlags enumerator",
|
controls.append(("call flag that is not an MGPipeCallFlags enumerator",
|
||||||
|
flag_typo_because,
|
||||||
lambda: check_call_flags_are_known([flag_typo])))
|
lambda: check_call_flags_are_known([flag_typo])))
|
||||||
controls.append(("kNone combined with a real flag",
|
controls.append(("kNone combined with a real flag",
|
||||||
|
"combines kNone with",
|
||||||
lambda: check_call_flags_are_known([flag_kNone])))
|
lambda: check_call_flags_are_known([flag_kNone])))
|
||||||
|
|
||||||
|
# R-16 meta-control (codex review finding 9; verified by execution in
|
||||||
|
# p5-results/wave1-codex-verify.md §9). The exact perturbation there replaced the
|
||||||
|
# flag-typo control's callback with `sys.exit("unrelated parser failure")` and,
|
||||||
|
# separately, with `sys.exit(0)`; the OLD expect_trip counted both as "tripped as
|
||||||
|
# expected" because it never looked at the message or the code. Both must be REJECTED
|
||||||
|
# here, under the real control's own `because`. This is a quiet, unlisted assertion (not
|
||||||
|
# one of the nine controls below) so it cannot itself inflate the trip count.
|
||||||
|
# "Red once by doing X" = reverting expect_trip to `except SystemExit as trip: return 1`
|
||||||
|
# (accept any SystemExit) - that alone turns each of these two checks from a silent pass
|
||||||
|
# into the sys.exit below.
|
||||||
|
if expect_trip("(R-16 meta-control) unrelated diagnostic standing in for the flag-typo guard",
|
||||||
|
flag_typo_because, lambda: sys.exit("unrelated parser failure"), quiet=True) != 0:
|
||||||
|
sys.exit("gen_pipe: self-test: expect_trip counted an UNRELATED SystemExit as the "
|
||||||
|
"flag-typo guard's own trip - finding 9 / R-16 is back")
|
||||||
|
if expect_trip("(R-16 meta-control) sys.exit(0) standing in for the flag-typo guard",
|
||||||
|
flag_typo_because, lambda: sys.exit(0), quiet=True) != 0:
|
||||||
|
sys.exit("gen_pipe: self-test: expect_trip counted sys.exit(0) as a trip - "
|
||||||
|
"finding 9 / R-16 is back")
|
||||||
|
|
||||||
trips = 0
|
trips = 0
|
||||||
for name, fn in controls:
|
for name, because, fn in controls:
|
||||||
trips += expect_trip(name, fn)
|
trips += expect_trip(name, because, fn)
|
||||||
# The positive control: the canned struct's exact list passes, and the parser sees the
|
# The positive control: the canned struct's exact list passes, and the parser sees the
|
||||||
# padding member as padding and the function as not a member.
|
# padding member as padding and the function as not a member.
|
||||||
check_field_lists_cover_struct_members({"Canned": ["A", "B", "C"]}, ["Canned"], [canned_struct])
|
check_field_lists_cover_struct_members({"Canned": ["A", "B", "C"]}, ["Canned"], [canned_struct])
|
||||||
|
|||||||
@@ -37,7 +37,20 @@
|
|||||||
# FlushPendingRangesNow at all, so a gate that hashed only that name protected text the shipping
|
# FlushPendingRangesNow at all, so a gate that hashed only that name protected text the shipping
|
||||||
# build never sees, and a tier-threshold edit made in the ladder that DOES ship would pass it. So
|
# build never sees, and a tier-threshold edit made in the ladder that DOES ship would pass it. So
|
||||||
# both names are hashed. The pull build's ladder is compared against the base ref; the push build's
|
# both names are hashed. The pull build's ladder is compared against the base ref; the push build's
|
||||||
# is compared against a sha pinned at the commit where it was reviewed - see PINNED_FUNCTIONS.
|
# is compared, ALWAYS AND WHETHER OR NOT <ref-a> DEFINES IT, against a sha pinned at the commit
|
||||||
|
# where that body was reviewed - see PINNED_FUNCTIONS.
|
||||||
|
#
|
||||||
|
# "ALWAYS" IS INTEGRATOR DECISION ID-41 AND IT IS A CHANGE. Until P5 this text said "pinned" while
|
||||||
|
# the implementation consulted the pin only as a FALLBACK, when <ref-a> did not define the function
|
||||||
|
# at all. At P3a's own base ref it does not, so the two readings agreed and nobody noticed; from
|
||||||
|
# ff2994d9 onward it does, so the fallback stopped firing and the row silently went back to being
|
||||||
|
# ref-a-vs-ref-b. ID-41 rules that the pin is the baseline, because the pin is the REVIEWED text:
|
||||||
|
# P5 (b1) gave this body two defaulted parameters (hostBaseFrom/hostBaseTo), a
|
||||||
|
# MOBILEGL_PIPE_VERIFY-only StageSnapshotTooNarrow log and a tier-1 access computation moved into
|
||||||
|
# InvalidateFlushAccessFor, the pull arm (FlushPendingRangesNow) is byte-identical across that
|
||||||
|
# change and G1 reports .text +0, so it is the intended change to the eleventh row rather than
|
||||||
|
# drift - and the answer is to RE-PIN it, not to revert it and not to let the row stop being
|
||||||
|
# compared. The OTHER TEN stay ref-a-vs-ref-b: nothing about them moved.
|
||||||
#
|
#
|
||||||
# THE TENTH IS HERE BY INTEGRATOR DECISION ID-11, resolving a contradiction inside the brief.
|
# THE TENTH IS HERE BY INTEGRATOR DECISION ID-11, resolving a contradiction inside the brief.
|
||||||
# D-F's "Decision: nine functions" table omits FlushPendingRangesNow, but BRIEF-P3A.md:420 calls it
|
# D-F's "Decision: nine functions" table omits FlushPendingRangesNow, but BRIEF-P3A.md:420 calls it
|
||||||
@@ -88,12 +101,22 @@ set -u -o pipefail
|
|||||||
SOURCE_PATH=MobileGL/MG_Backend/DirectGLES/Managers.cpp
|
SOURCE_PATH=MobileGL/MG_Backend/DirectGLES/Managers.cpp
|
||||||
# The ten that exist at the P3a base ref, so their baseline is read out of <ref-a>.
|
# The ten that exist at the P3a base ref, so their baseline is read out of <ref-a>.
|
||||||
FUNCTIONS="IsPoolable EnrollIntoPool AcquireFromPool TrimBufferPool ClearBufferPool ProcessDeferredBufferReleases CreateRingStorage RingAvailable RingAllocate FlushPendingRangesNow"
|
FUNCTIONS="IsPoolable EnrollIntoPool AcquireFromPool TrimBufferPool ClearBufferPool ProcessDeferredBufferReleases CreateRingStorage RingAvailable RingAllocate FlushPendingRangesNow"
|
||||||
# The ELEVENTH (ID-15), and it is a different kind of row: it was BORN in P3a, so there is no
|
# The ELEVENTH (ID-15), and it is a different kind of row: it was BORN in P3a, so there was no
|
||||||
# body at the base ref to compare it with and its baseline is PINNED below, captured at
|
# body at the base ref to compare it with, and its baseline is PINNED below and consulted
|
||||||
# 3e298c9a - the commit at which the two-arm shape was reviewed and accepted.
|
# UNCONDITIONALLY (ID-41 - see the long note at the top of this file).
|
||||||
|
#
|
||||||
|
# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and
|
||||||
|
# writes the decision beside them; a pin with no commit and no decision next to it is a number
|
||||||
|
# nobody can audit.
|
||||||
|
# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted
|
||||||
|
# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good
|
||||||
|
# for a parameter of the flush ladder, so tier 1's widening refusal is
|
||||||
|
# live code the moment a SEG_STAGE snapshot is narrower than the queued
|
||||||
|
# range. <- CURRENT
|
||||||
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
||||||
PINNED_BASELINE_REF=3e298c9a
|
PINNED_BASELINE_REF=3dadd4c1
|
||||||
PINNED_SHA_FlushPendingRangesFrom=37fc94ffc5991923d222d585daa3af6511d2352d255623026ce35a3b6963c4a6
|
PINNED_BASELINE_DECISION=ID-41
|
||||||
|
PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0
|
||||||
ALL_FUNCTIONS="$FUNCTIONS $PINNED_FUNCTIONS"
|
ALL_FUNCTIONS="$FUNCTIONS $PINNED_FUNCTIONS"
|
||||||
EXPECTED_FUNCTION_COUNT=11
|
EXPECTED_FUNCTION_COUNT=11
|
||||||
# The functions the self-test perturbs, one control each. ClearBufferPool is small, has no forward
|
# The functions the self-test perturbs, one control each. ClearBufferPool is small, has no forward
|
||||||
@@ -247,7 +270,7 @@ def extract(path, names):
|
|||||||
return rows, problems
|
return rows, problems
|
||||||
|
|
||||||
|
|
||||||
def perturb(src, dst, names, target):
|
def perturb(src, dst, names, target, one_token=False):
|
||||||
text = open(src, encoding='utf-8', newline='').read()
|
text = open(src, encoding='utf-8', newline='').read()
|
||||||
masked = mask(text)
|
masked = mask(text)
|
||||||
hits = find_definition(text, masked, target)
|
hits = find_definition(text, masked, target)
|
||||||
@@ -260,9 +283,17 @@ def perturb(src, dst, names, target):
|
|||||||
# perturbation somewhere that is not the body, and the control would be proving the wrong
|
# perturbation somewhere that is not the body, and the control would be proving the wrong
|
||||||
# thing. Offsets are identical between the two by construction (mask() preserves length).
|
# thing. Offsets are identical between the two by construction (mask() preserves length).
|
||||||
brace = masked.index('{', begin)
|
brace = masked.index('{', begin)
|
||||||
patched = (text[:brace + 1] +
|
if one_token:
|
||||||
'\n // p3a_untouched_regions.sh --self-test: a body that MOVED.\n' +
|
# ONE TOKEN - a single empty statement - and nothing else. ID-41(d) asks the pinned row's
|
||||||
text[brace + 1:])
|
# control to perturb the LADDER rather than a comment beside it, and this is the smallest
|
||||||
|
# edit that is unambiguously code: a reader cannot answer "the gate only notices comments".
|
||||||
|
# The perturbed copy is never compiled, only hashed, so an empty statement is legal here in
|
||||||
|
# a way it would not be in the tree.
|
||||||
|
patched = text[:brace + 1] + ';' + text[brace + 1:]
|
||||||
|
else:
|
||||||
|
patched = (text[:brace + 1] +
|
||||||
|
'\n // p3a_untouched_regions.sh --self-test: a body that MOVED.\n' +
|
||||||
|
text[brace + 1:])
|
||||||
open(dst, 'w', encoding='utf-8', newline='').write(patched)
|
open(dst, 'w', encoding='utf-8', newline='').write(patched)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -282,8 +313,8 @@ def main(argv):
|
|||||||
for sha, name in rows:
|
for sha, name in rows:
|
||||||
sys.stdout.write('%s %s\n' % (sha, name))
|
sys.stdout.write('%s %s\n' % (sha, name))
|
||||||
return 2 if problems else 0
|
return 2 if problems else 0
|
||||||
if mode == 'perturb':
|
if mode in ('perturb', 'perturb-token'):
|
||||||
return perturb(argv[2], argv[3], names, argv[4])
|
return perturb(argv[2], argv[3], names, argv[4], mode == 'perturb-token')
|
||||||
sys.stderr.write('[p3a-untouched] unknown mode %r\n' % mode)
|
sys.stderr.write('[p3a-untouched] unknown mode %r\n' % mode)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
@@ -304,38 +335,74 @@ extract_ref() {
|
|||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
# The BASELINE side (<ref-a>). Same extraction, with one difference that the eleventh row makes
|
# True when $1 is one of the rows whose baseline is the PIN rather than <ref-a>.
|
||||||
# necessary: a function born in P3a has no body at the P3a base ref, and CI passes exactly that
|
is_pinned_row() {
|
||||||
# ref as <ref-a>. Asking for it there is not "the gate could not run" - it is the expected
|
local name candidate
|
||||||
# answer - so a name in PINNED_FUNCTIONS that is missing at <ref-a> takes the sha pinned at the
|
for candidate in $PINNED_FUNCTIONS; do
|
||||||
# top of this script instead. Everything else still has to be found: the fallback is entered
|
[ "$candidate" = "$1" ] && return 0
|
||||||
# only after a strict extraction failed, and it then re-extracts the pre-P3a ten strictly, so a
|
done
|
||||||
# genuine rename of one of THOSE is still exit 2 rather than a silently short list.
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Overwrite the pinned rows of a `<sha> <name>` list with the shas PINNED at the top of this
|
||||||
|
# script. ONE spelling of the substitution, called both by extract_baseline and by --self-test's
|
||||||
|
# pin controls, so the control drives the gate's own path instead of re-spelling it - a control
|
||||||
|
# that re-implements what it checks proves only that the copy agrees with itself.
|
||||||
|
apply_pinned_shas() {
|
||||||
|
local file=$1 name pinned
|
||||||
|
for name in $PINNED_FUNCTIONS; do
|
||||||
|
eval "pinned=\$PINNED_SHA_$name"
|
||||||
|
if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then
|
||||||
|
say "$name has no pinned baseline sha; the eleventh row cannot be compared"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
grep -v " $name\$" "$file" > "$file.unpinned" || true
|
||||||
|
mv -f "$file.unpinned" "$file" || return 2
|
||||||
|
# Appended LAST, which is also its position in the fixed order (it is the eleventh of eleven),
|
||||||
|
# so the header's "stdout is always the sha list in the fixed order" stays true and a baseline
|
||||||
|
# captured by redirect still diffs cleanly against a two-ref run.
|
||||||
|
printf '%s %s\n' "$pinned" "$name" >> "$file"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# The BASELINE side (<ref-a>). The TEN are extracted strictly, so a rename of one of THOSE is
|
||||||
|
# exit 2 rather than a silently short list. FlushPendingRangesFrom then takes the PINNED sha
|
||||||
|
# WHETHER OR NOT <ref-a> defines it (ID-41), and a <ref-a> that defines it DIFFERENTLY is
|
||||||
|
# reported - loudly - because the two answers disagreeing is itself a finding rather than a
|
||||||
|
# reason to prefer the ref.
|
||||||
extract_baseline() {
|
extract_baseline() {
|
||||||
local ref=$1 out=$2 blob="$WORK_DIR/$2.cpp" name sha
|
local ref=$1 out=$2 blob="$WORK_DIR/$2.cpp" name pinned atRef
|
||||||
if ! git show "$ref:$SOURCE_PATH" > "$blob" 2>"$WORK_DIR/show.err"; then
|
if ! git show "$ref:$SOURCE_PATH" > "$blob" 2>"$WORK_DIR/show.err"; then
|
||||||
say "cannot read $SOURCE_PATH at '$ref':"
|
say "cannot read $SOURCE_PATH at '$ref':"
|
||||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/show.err" >&2
|
sed 's/^/[p3a-untouched] /' "$WORK_DIR/show.err" >&2
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
if python3 "$PY" extract "$blob" "$ALL_FUNCTIONS" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then
|
if ! python3 "$PY" extract "$blob" "$FUNCTIONS" > "$WORK_DIR/$out.sha" 2>"$WORK_DIR/$out.err"; then
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if ! python3 "$PY" extract "$blob" "$FUNCTIONS" > "$WORK_DIR/$out.sha"; then
|
|
||||||
say "the baseline ref '$ref' does not define the pre-P3a ten exactly once each:"
|
say "the baseline ref '$ref' does not define the pre-P3a ten exactly once each:"
|
||||||
sed 's/^/[p3a-untouched] /' "$WORK_DIR/$out.err" >&2
|
sed 's/^/[p3a-untouched] /' "$WORK_DIR/$out.err" >&2
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
for name in $PINNED_FUNCTIONS; do
|
for name in $PINNED_FUNCTIONS; do
|
||||||
eval "sha=\$PINNED_SHA_$name"
|
eval "pinned=\$PINNED_SHA_$name"
|
||||||
if [ -z "$sha" ] || [ "$sha" = "PLACEHOLDER_SHA" ]; then
|
atRef=$(python3 "$PY" extract "$blob" "$name" 2>/dev/null | awk -v n="$name" '$2 == n { print $1 }')
|
||||||
say "$name has no pinned baseline sha; the eleventh row cannot be compared"
|
if [ -z "$atRef" ]; then
|
||||||
return 2
|
say "$name is not defined at '$ref' (it was born in P3a): its baseline is the sha PINNED in"
|
||||||
|
say " this script, captured at $PINNED_BASELINE_REF ($PINNED_BASELINE_DECISION)"
|
||||||
|
elif [ "$atRef" != "$pinned" ]; then
|
||||||
|
say "NOTE: $name IS defined at '$ref' and hashes"
|
||||||
|
say " $atRef, which is not the pin"
|
||||||
|
say " ($pinned,"
|
||||||
|
say " captured at $PINNED_BASELINE_REF, $PINNED_BASELINE_DECISION). THE PIN IS WHAT IS"
|
||||||
|
say " COMPARED - it is the reviewed body - and this note is not a verdict in either"
|
||||||
|
say " direction. If '$ref' PREDATES $PINNED_BASELINE_REF the two SHOULD disagree: the pinned"
|
||||||
|
say " body is the change $PINNED_BASELINE_DECISION admitted, which is why it was re-pinned"
|
||||||
|
say " rather than reverted. If it does not predate it, the ladder that ships has moved away"
|
||||||
|
say " from the reviewed text without this gate being re-pinned - re-pin deliberately or"
|
||||||
|
say " revert, but do not leave them disagreeing."
|
||||||
fi
|
fi
|
||||||
printf '%s %s\n' "$sha" "$name" >> "$WORK_DIR/$out.sha"
|
|
||||||
say "$name is not defined at '$ref' (it was born in P3a): its baseline is the sha PINNED in"
|
|
||||||
say " this script, captured at $PINNED_BASELINE_REF"
|
|
||||||
done
|
done
|
||||||
|
apply_pinned_shas "$WORK_DIR/$out.sha" || return 2
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +417,16 @@ compare_lists() {
|
|||||||
say "FIRST FUNCTION THAT MOVED: $name"
|
say "FIRST FUNCTION THAT MOVED: $name"
|
||||||
say " $labelA $shaA"
|
say " $labelA $shaA"
|
||||||
say " $labelB ${shaB:-<not found>}"
|
say " $labelB ${shaB:-<not found>}"
|
||||||
|
if is_pinned_row "$name"; then
|
||||||
|
# ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned row and the ten
|
||||||
|
# ref-a rows fail differently and are fixed differently, so a reader who sees only the
|
||||||
|
# generic paragraph below goes looking for a diff against <ref-a> that does not exist.
|
||||||
|
say " $name IS A PINNED ROW ($PINNED_BASELINE_DECISION): its baseline is ALWAYS the sha"
|
||||||
|
say " PINNED in this script - the body reviewed at $PINNED_BASELINE_REF - and never the"
|
||||||
|
say " body at '$labelA'. So this is not a diff against the base ref: the ladder that"
|
||||||
|
say " SHIPS has moved away from the text that was reviewed. Either re-pin deliberately,"
|
||||||
|
say " replacing the sha AND the commit AND the decision beside it, or revert the body."
|
||||||
|
fi
|
||||||
say " G5 (ARCHITECTURE.md:316, :515) says the buffer pool, the deferred-release drain, the"
|
say " G5 (ARCHITECTURE.md:316, :515) says the buffer pool, the deferred-release drain, the"
|
||||||
say " three rings and BOTH arms of the three-tier flush drain - FlushPendingRangesNow in"
|
say " three rings and BOTH arms of the three-tier flush drain - FlushPendingRangesNow in"
|
||||||
say " the pull build, FlushPendingRangesFrom in the push build (BRIEF-P3A.md:420, :1708,"
|
say " the pull build, FlushPendingRangesFrom in the push build (BRIEF-P3A.md:420, :1708,"
|
||||||
@@ -436,6 +513,81 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
fi
|
fi
|
||||||
say "negative control: a perturbed $target body is reported, and named"
|
say "negative control: a perturbed $target body is reported, and named"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# --- THE PINNED ROW (ID-41) -----------------------------------------------------------------
|
||||||
|
# TWO more controls, and they exist because none of the five above can see the pin at all: every
|
||||||
|
# one of them compares one extraction of the working tree against another, so they would all be
|
||||||
|
# green on a build of this script in which PINNED_SHA_* was never read by anything. The eleventh
|
||||||
|
# row's whole claim is "the baseline is the PIN, not <ref-a>", and that claim needs its own two.
|
||||||
|
for target in $PINNED_FUNCTIONS; do
|
||||||
|
eval "pinned=\$PINNED_SHA_$target"
|
||||||
|
|
||||||
|
# (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row -
|
||||||
|
# which is the shape of every <ref-a> CI passes today, since ff2994d9 and 37da3c3a both DEFINE
|
||||||
|
# FlushPendingRangesFrom - must come out of apply_pinned_shas carrying the PIN. This is the
|
||||||
|
# control that would have caught the ID-41 defect itself: before it, the pin was consulted
|
||||||
|
# only when <ref-a> lacked the function, so the row silently reverted to ref-a-vs-ref-b the
|
||||||
|
# moment a base ref had one.
|
||||||
|
grep -v " $target\$" "$WORK_DIR/pristine.sha" > "$WORK_DIR/pinprec.sha" || true
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||||
|
>> "$WORK_DIR/pinprec.sha"
|
||||||
|
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
||||||
|
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||||
|
if [ "$got" != "$pinned" ]; then
|
||||||
|
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||||
|
say " '${got:-<absent>}' and not as the pin ($pinned). The eleventh row would be compared"
|
||||||
|
say " against <ref-a> again, which is exactly the defect $PINNED_BASELINE_DECISION closed."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
say "pin control: a baseline that defines $target differently is overridden by the PIN"
|
||||||
|
|
||||||
|
# (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go
|
||||||
|
# red, must name the row, and must say that the row is PINNED - R-16's "a control asserts its
|
||||||
|
# OWN failure string": the pinned row and the ten ref-a rows are fixed differently, and a
|
||||||
|
# reader who gets only the generic paragraph goes looking for a diff against <ref-a> that does
|
||||||
|
# not exist.
|
||||||
|
python3 "$PY" perturb-token "$WORK_DIR/pristine.cpp" "$WORK_DIR/pinperturbed.cpp" \
|
||||||
|
"$target" "$ALL_FUNCTIONS" || exit 2
|
||||||
|
python3 "$PY" extract "$WORK_DIR/pinperturbed.cpp" "$ALL_FUNCTIONS" \
|
||||||
|
> "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||||
|
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
|
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
|
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||||
|
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||||
|
say "comparison AGAINST THE PIN still reported every function as identical. The pinned row is"
|
||||||
|
say "not being compared at all, so every green this gate has printed for it means nothing."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! grep -q "FIRST FUNCTION THAT MOVED: $target" "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: the comparison against the pin went red"
|
||||||
|
say "but did not name $target as the first function that moved. It said:"
|
||||||
|
sed 's/^/[p3a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! grep -q "$target IS A PINNED ROW" "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: it went red and named $target, but did"
|
||||||
|
say "not say that this row's baseline is the PIN. That is the half a reader acts on, and a"
|
||||||
|
say "control that does not assert its own message is not a control (R-16). It said:"
|
||||||
|
sed 's/^/[p3a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
say "negative control: a ONE-TOKEN edit to the pinned $target body goes red AGAINST THE PIN,"
|
||||||
|
say " is named, and says the row is pinned"
|
||||||
|
|
||||||
|
# NOT a failure, deliberately: --self-test is about whether the comparison works, and it runs
|
||||||
|
# on the WORKING tree, which may legitimately carry an uncommitted edit. The two-ref gate is
|
||||||
|
# what fails when the committed ladder has left the pin. But say so, because a self-test that
|
||||||
|
# was green on a tree whose ladder no longer matches its pin is confusing in exactly one
|
||||||
|
# direction.
|
||||||
|
treeSha=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pristine.sha")
|
||||||
|
if [ "$treeSha" != "$pinned" ]; then
|
||||||
|
say "NOTE: this working tree's $target hashes $treeSha, not the pin ($pinned). The"
|
||||||
|
say " self-test's verdict is unaffected; the two-ref gate will be RED until you re-pin or"
|
||||||
|
say " revert."
|
||||||
|
fi
|
||||||
|
done
|
||||||
say "self-test passed"
|
say "self-test passed"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -110,12 +110,21 @@
|
|||||||
# next reader of this file meets them:
|
# next reader of this file meets them:
|
||||||
# D-N/1 the namespace region kind, above.
|
# D-N/1 the namespace region kind, above.
|
||||||
# D-N/2 the PINNED baseline is CONSULTED UNCONDITIONALLY for FlushPendingRangesFrom, where the
|
# D-N/2 the PINNED baseline is CONSULTED UNCONDITIONALLY for FlushPendingRangesFrom, where the
|
||||||
# parent consults it only when <ref-a> does not define the function. D-N says that row is
|
# parent consulted it only when <ref-a> did not define the function. D-N says that row is
|
||||||
# compared "against its pinned 3e298c9a sha", and at P4a's base ref the function DOES
|
# compared "against its pinned sha", and at P4a's base ref the function DOES exist - so
|
||||||
# exist - so the parent's fallback would silently never fire and the pin would stop being
|
# the parent's fallback would silently never fire and the pin would stop being the
|
||||||
# the baseline the brief names. Both readings agree on this tree (measured: the body at
|
# baseline the brief names. INTEGRATOR DECISION ID-41 has since made this the parent's
|
||||||
# 37da3c3a hashes to the pinned value); where they would ever disagree, this script says
|
# reading too, so D-N/2 is no longer a deviation between the two scripts; it is kept here
|
||||||
# so on stderr and keeps the PIN, because the pin is the reviewed text.
|
# as the record of why this one got there first.
|
||||||
|
#
|
||||||
|
# The two answers now DISAGREE on every ref CI passes, and that is the expected state
|
||||||
|
# rather than a finding: P5 (b1) re-parameterised the shipping ladder
|
||||||
|
# (hostBaseFrom/hostBaseTo, a MOBILEGL_PIPE_VERIFY-only StageSnapshotTooNarrow log, the
|
||||||
|
# tier-1 access computation moved into InvalidateFlushAccessFor), the PULL arm is
|
||||||
|
# byte-identical across that change and G1 reports .text +0, and ID-41 ruled that the row
|
||||||
|
# be RE-PINNED on the reviewed P5 body rather than reverted or quietly left comparing
|
||||||
|
# against <ref-a>. This script says so on stderr, in both directions, and keeps the PIN -
|
||||||
|
# because the pin is the reviewed text.
|
||||||
set -u -o pipefail
|
set -u -o pipefail
|
||||||
|
|
||||||
# One row per region: <name>@<kind>@<path>. The ORDER is the fixed order the sha list is printed
|
# One row per region: <name>@<kind>@<path>. The ORDER is the fixed order the sha list is printed
|
||||||
@@ -143,11 +152,20 @@ ShouldUseCaveatTextureFormat@function@MobileGL/MG_Backend/DirectGLES/Utils.cpp"
|
|||||||
EXPECTED_FUNCTION_COUNT=17
|
EXPECTED_FUNCTION_COUNT=17
|
||||||
|
|
||||||
# The one region born in P3a, so there is no body at P4a's base ref that this phase reviewed: its
|
# The one region born in P3a, so there is no body at P4a's base ref that this phase reviewed: its
|
||||||
# baseline is the sha captured at 3e298c9a, the commit at which the two-arm shape was reviewed and
|
# baseline is PINNED and consulted UNCONDITIONALLY (DEVIATIONS D-N/2, and ID-41 for the parent).
|
||||||
# accepted (ID-15). See DEVIATIONS D-N/2 for why it is consulted unconditionally.
|
#
|
||||||
|
# THE PIN, AND WHAT RE-PINS IT. Whoever moves this body deliberately replaces BOTH lines and
|
||||||
|
# writes the decision beside them; a pin with no commit and no decision next to it is a number
|
||||||
|
# nobody can audit. The parent script carries the identical table and the two must not drift.
|
||||||
|
# 3e298c9a 37fc94ff... ID-15, P3a: the two-arm shape, reviewed and accepted
|
||||||
|
# 3dadd4c1 172b0222... ID-41, P5 (b1): [Fix] (DirectGLES): make the extent hostBase is good
|
||||||
|
# for a parameter of the flush ladder, so tier 1's widening refusal is
|
||||||
|
# live code the moment a SEG_STAGE snapshot is narrower than the queued
|
||||||
|
# range. <- CURRENT
|
||||||
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
PINNED_FUNCTIONS="FlushPendingRangesFrom"
|
||||||
PINNED_BASELINE_REF=3e298c9a
|
PINNED_BASELINE_REF=3dadd4c1
|
||||||
PINNED_SHA_FlushPendingRangesFrom=37fc94ffc5991923d222d585daa3af6511d2352d255623026ce35a3b6963c4a6
|
PINNED_BASELINE_DECISION=ID-41
|
||||||
|
PINNED_SHA_FlushPendingRangesFrom=172b022273db01b16e772d15b269ffcd797fe38c767f7354d83ce113a66040d0
|
||||||
|
|
||||||
# The regions the self-test perturbs, one negative control each. FOUR, exactly as D-N requires, and
|
# The regions the self-test perturbs, one negative control each. FOUR, exactly as D-N requires, and
|
||||||
# each is a different shape so that a control which only ever perturbed the easy one cannot leave
|
# each is a different shape so that a control which only ever perturbed the easy one cannot leave
|
||||||
@@ -372,7 +390,7 @@ def extract(rows):
|
|||||||
|
|
||||||
|
|
||||||
def perturb(rows, target, src, dst, where='head'):
|
def perturb(rows, target, src, dst, where='head'):
|
||||||
"""Insert one line into a region's body, at its HEAD or at its TAIL.
|
"""Insert one line into a region's body at its HEAD or its TAIL, or one TOKEN at its head.
|
||||||
|
|
||||||
TWO POSITIONS, AND THE SECOND ONE IS REVIEW FINDING F-m2. Every control used to insert at the
|
TWO POSITIONS, AND THE SECOND ONE IS REVIEW FINDING F-m2. Every control used to insert at the
|
||||||
very first byte after the opening brace, so all four of them would still have tripped if
|
very first byte after the opening brace, so all four of them would still have tripped if
|
||||||
@@ -393,7 +411,15 @@ def perturb(rows, target, src, dst, where='head'):
|
|||||||
% (target, len(hits)))
|
% (target, len(hits)))
|
||||||
return 2
|
return 2
|
||||||
begin, end = hits[0]
|
begin, end = hits[0]
|
||||||
if where == 'tail':
|
if where == 'token':
|
||||||
|
# ONE TOKEN - a single empty statement at the head of the body - and nothing else.
|
||||||
|
# ID-41(d) asks the pinned row's control to perturb the LADDER rather than a comment
|
||||||
|
# beside it, and this is the smallest edit that is unambiguously code: a reader cannot
|
||||||
|
# answer "the gate only notices comments". The perturbed copy is never compiled, only
|
||||||
|
# hashed, so an empty statement is legal here in a way it would not be in the tree.
|
||||||
|
brace = masked.index('{', begin)
|
||||||
|
patched = text[:brace + 1] + ';' + text[brace + 1:]
|
||||||
|
elif where == 'tail':
|
||||||
# end is one PAST the closing brace (find_function / find_namespace both return
|
# end is one PAST the closing brace (find_function / find_namespace both return
|
||||||
# `match_forward(...) + 1`), so end - 1 is the brace itself and this lands inside the
|
# `match_forward(...) + 1`), so end - 1 is the brace itself and this lands inside the
|
||||||
# body, one character before it ends.
|
# body, one character before it ends.
|
||||||
@@ -495,31 +521,59 @@ extract_baseline() {
|
|||||||
sed 's/^/[p4a-untouched] /' "$WORK_DIR/$out.err" >&2
|
sed 's/^/[p4a-untouched] /' "$WORK_DIR/$out.err" >&2
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
|
for name in $PINNED_FUNCTIONS; do
|
||||||
|
eval "pinned=\$PINNED_SHA_$name"
|
||||||
|
grep "^$name$(printf '\t')" "$WORK_DIR/$out.spec.all" > "$WORK_DIR/$out.spec.pinned" || true
|
||||||
|
atRef=$(python3 "$PY" extract "$WORK_DIR/$out.spec.pinned" 2>/dev/null | awk '{ print $1 }')
|
||||||
|
if [ -n "$atRef" ] && [ "$atRef" != "$pinned" ]; then
|
||||||
|
say "NOTE: $name IS defined at '$ref' and hashes"
|
||||||
|
say " $atRef, which is not the pin"
|
||||||
|
say " ($pinned,"
|
||||||
|
say " captured at $PINNED_BASELINE_REF, $PINNED_BASELINE_DECISION). THE PIN IS WHAT IS"
|
||||||
|
say " COMPARED - it is the reviewed body - and this note is not a verdict in either"
|
||||||
|
say " direction. If '$ref' PREDATES $PINNED_BASELINE_REF the two SHOULD disagree: the pinned"
|
||||||
|
say " body is the change $PINNED_BASELINE_DECISION admitted, which is why it was re-pinned"
|
||||||
|
say " rather than reverted. If it does not predate it, the ladder that ships has moved away"
|
||||||
|
say " from the reviewed text without this gate being re-pinned - re-pin deliberately or"
|
||||||
|
say " revert, but do not leave them disagreeing."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
# The pinned rows are appended and the list is put back into the FIXED ORDER, both inside
|
||||||
|
# apply_pinned_shas - ONE spelling of the substitution, which --self-test's pin controls drive
|
||||||
|
# as well, so a control cannot prove only that a copy of the logic agrees with itself.
|
||||||
|
apply_pinned_shas "$WORK_DIR/$out.sha" || return 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# True when $1 is one of the rows whose baseline is the PIN rather than <ref-a>.
|
||||||
|
is_pinned_row() {
|
||||||
|
local name candidate
|
||||||
|
for candidate in $PINNED_FUNCTIONS; do
|
||||||
|
[ "$candidate" = "$1" ] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Overwrite the pinned rows of a `<sha> <region>` list with the shas PINNED at the top of this
|
||||||
|
# script, then restore the FIXED ORDER (review F-m1: the pinned rows would otherwise be emitted
|
||||||
|
# LAST while extract_ref emits everything in REGIONS order. The gate itself never noticed -
|
||||||
|
# compare_lists looks rows up by name - but the documented capture workflow did: the header
|
||||||
|
# promises "stdout is always the sha list ... in the fixed order above - so a baseline capture is a
|
||||||
|
# plain redirect", and a baseline captured that way then diffed against a two-ref stdout showed
|
||||||
|
# seven spurious differences purely from row order).
|
||||||
|
apply_pinned_shas() {
|
||||||
|
local file=$1 name pinned
|
||||||
for name in $PINNED_FUNCTIONS; do
|
for name in $PINNED_FUNCTIONS; do
|
||||||
eval "pinned=\$PINNED_SHA_$name"
|
eval "pinned=\$PINNED_SHA_$name"
|
||||||
if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then
|
if [ -z "$pinned" ] || [ "$pinned" = "PLACEHOLDER_SHA" ]; then
|
||||||
say "$name has no pinned baseline sha; that row cannot be compared"
|
say "$name has no pinned baseline sha; that row cannot be compared"
|
||||||
return 2
|
return 2
|
||||||
fi
|
fi
|
||||||
grep "^$name$(printf '\t')" "$WORK_DIR/$out.spec.all" > "$WORK_DIR/$out.spec.pinned" || true
|
grep -v " $name\$" "$file" > "$file.unpinned" || true
|
||||||
atRef=$(python3 "$PY" extract "$WORK_DIR/$out.spec.pinned" 2>/dev/null | awk '{ print $1 }')
|
mv -f "$file.unpinned" "$file" || return 2
|
||||||
if [ -n "$atRef" ] && [ "$atRef" != "$pinned" ]; then
|
printf '%s %s\n' "$pinned" "$name" >> "$file"
|
||||||
say "NOTE: $name IS defined at '$ref' and hashes $atRef, which is NOT the sha pinned in this"
|
|
||||||
say " script ($pinned, captured at $PINNED_BASELINE_REF). The PIN is what is compared - it is"
|
|
||||||
say " the reviewed text (ID-15) - but the two disagreeing means the push ladder moved between"
|
|
||||||
say " $PINNED_BASELINE_REF and '$ref' without this gate being re-pinned. Re-pin deliberately or"
|
|
||||||
say " revert; do not leave them disagreeing."
|
|
||||||
fi
|
|
||||||
printf '%s %s\n' "$pinned" "$name" >> "$WORK_DIR/$out.sha"
|
|
||||||
done
|
done
|
||||||
# ...and put the list back into the FIXED ORDER (review F-m1). The pinned rows were stripped out
|
reorder_sha_list "$file" || return 2
|
||||||
# of the spec above and appended here, so without this the baseline side emits them LAST while
|
|
||||||
# extract_ref emits everything in REGIONS order. The gate itself never noticed - compare_lists
|
|
||||||
# looks rows up by name - but the documented capture workflow did: the header promises "stdout is
|
|
||||||
# always the sha list ... in the fixed order above - so a baseline capture is a plain redirect",
|
|
||||||
# and a baseline captured that way then diffed against a two-ref stdout showed seven spurious
|
|
||||||
# differences purely from row order.
|
|
||||||
reorder_sha_list "$WORK_DIR/$out.sha" || return 2
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -553,6 +607,18 @@ compare_lists() {
|
|||||||
say "FIRST REGION THAT MOVED: $name"
|
say "FIRST REGION THAT MOVED: $name"
|
||||||
say " $labelA ${shaA:-<not found>}"
|
say " $labelA ${shaA:-<not found>}"
|
||||||
say " $labelB ${shaB:-<not found>}"
|
say " $labelB ${shaB:-<not found>}"
|
||||||
|
if is_pinned_row "$name"; then
|
||||||
|
# ITS OWN MESSAGE, and that is R-16 rather than decoration: the pinned row and the
|
||||||
|
# sixteen ref-a rows fail differently and are fixed differently, so a reader who sees
|
||||||
|
# only the generic paragraph below goes looking for a diff against <ref-a> that does not
|
||||||
|
# exist.
|
||||||
|
say " $name IS A PINNED ROW ($PINNED_BASELINE_DECISION): its baseline is ALWAYS the sha"
|
||||||
|
say " PINNED in this script - the body reviewed at $PINNED_BASELINE_REF - and never the"
|
||||||
|
say " body at '$labelA'. So this is not a diff against the base ref: the ladder that"
|
||||||
|
say " SHIPS has moved away from the text that was reviewed. Either re-pin deliberately,"
|
||||||
|
say " replacing the sha AND the commit AND the decision beside it in BOTH this script"
|
||||||
|
say " and its parent scripts/p3a_untouched_regions.sh, or revert the body."
|
||||||
|
fi
|
||||||
say " G5 (ARCHITECTURE.md:318, :321, :515) says the Espryt do-not-touch list is literal:"
|
say " G5 (ARCHITECTURE.md:318, :321, :515) says the Espryt do-not-touch list is literal:"
|
||||||
say " P3a's buffer pool, deferred-release drain, three rings and BOTH arms of the three-tier"
|
say " P3a's buffer pool, deferred-release drain, three rings and BOTH arms of the three-tier"
|
||||||
say " flush drain, plus P4a's unpack-PBO staging repack and its two ring helpers, the"
|
say " flush drain, plus P4a's unpack-PBO staging repack and its two ring helpers, the"
|
||||||
@@ -573,10 +639,12 @@ compare_lists() {
|
|||||||
# --- self-test ------------------------------------------------------------------------------
|
# --- self-test ------------------------------------------------------------------------------
|
||||||
# A gate that always says "identical" and a gate that is working produce the same green, so the
|
# A gate that always says "identical" and a gate that is working produce the same green, so the
|
||||||
# comparison has to be shown failing. Both controls run: the POSITIVE ones (an untouched copy
|
# comparison has to be shown failing. Both controls run: the POSITIVE ones (an untouched copy
|
||||||
# compares equal; an edit OUTSIDE the regions is invisible) rule out a comparison that reports
|
# compares equal; an edit OUTSIDE the regions is invisible; a baseline that names another sha for
|
||||||
# every region as moved, and the eight NEGATIVE ones - D-N's four regions, each perturbed at the
|
# the PINNED row is overridden by the pin) rule out a comparison that reports every region as
|
||||||
# HEAD of its body and again at its TAIL - rule out both the comparison that never reports any and
|
# moved, and the NINE NEGATIVE ones - D-N's four regions, each perturbed at the HEAD of its body
|
||||||
# the extraction whose extent stops before the closing brace (F-m2).
|
# and again at its TAIL, plus a ONE-TOKEN edit to the pinned ladder compared AGAINST THE PIN
|
||||||
|
# (ID-41) - rule out the comparison that never reports any, the extraction whose extent stops
|
||||||
|
# before the closing brace (F-m2), and a pin that nothing consults.
|
||||||
if [ "${1:-}" = "--self-test" ]; then
|
if [ "${1:-}" = "--self-test" ]; then
|
||||||
[ $# -eq 1 ] || { say "--self-test takes no other arguments"; exit 2; }
|
[ $# -eq 1 ] || { say "--self-test takes no other arguments"; exit 2; }
|
||||||
mkdir -p "$WORK_DIR/pristine" || exit 2
|
mkdir -p "$WORK_DIR/pristine" || exit 2
|
||||||
@@ -678,6 +746,85 @@ if [ "${1:-}" = "--self-test" ]; then
|
|||||||
say "its tail), ran $controls"
|
say "its tail), ran $controls"
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# --- THE PINNED ROW (ID-41) -----------------------------------------------------------------
|
||||||
|
# TWO more controls, and they exist because none of the ten above can see the pin at all: every
|
||||||
|
# one of them compares one extraction of the working tree against another, so they would all be
|
||||||
|
# green on a build of this script in which PINNED_SHA_* was never read by anything. The pinned
|
||||||
|
# row's whole claim is "the baseline is the PIN, not <ref-a>" (D-N/2, ID-41), and that claim
|
||||||
|
# needs its own two.
|
||||||
|
for target in $PINNED_FUNCTIONS; do
|
||||||
|
eval "pinned=\$PINNED_SHA_$target"
|
||||||
|
targetSource=$(printf '%s\n' "$REGIONS" | awk -F@ -v n="$target" '$1 == n { print $3 }')
|
||||||
|
[ -n "$targetSource" ] || { say "$target is not one of the regions"; exit 2; }
|
||||||
|
|
||||||
|
# (1) PIN PRECEDENCE, positive. A baseline that carries some OTHER sha for the pinned row -
|
||||||
|
# which is the shape of every <ref-a> CI passes, since 37da3c3a DOES define
|
||||||
|
# FlushPendingRangesFrom and no longer hashes the pin - must come out of apply_pinned_shas
|
||||||
|
# carrying the PIN.
|
||||||
|
grep -v " $target\$" "$WORK_DIR/pristine.sha" > "$WORK_DIR/pinprec.sha" || true
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" "$target" \
|
||||||
|
>> "$WORK_DIR/pinprec.sha"
|
||||||
|
apply_pinned_shas "$WORK_DIR/pinprec.sha" || exit 2
|
||||||
|
got=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pinprec.sha")
|
||||||
|
if [ "$got" != "$pinned" ]; then
|
||||||
|
say "PIN CONTROL FAILED: a baseline that carried a DIFFERENT sha for $target came out as"
|
||||||
|
say " '${got:-<absent>}' and not as the pin ($pinned). That row would be compared against"
|
||||||
|
say " <ref-a> again, which is exactly what D-N/2 and $PINNED_BASELINE_DECISION forbid."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
say "pin control: a baseline that defines $target differently is overridden by the PIN"
|
||||||
|
|
||||||
|
# (2) A ONE-TOKEN EDIT TO THE PINNED LADDER, negative, AGAINST THE PIN. The comparison must go
|
||||||
|
# red, must name the region, and must say the region is PINNED - R-16's "a control asserts its
|
||||||
|
# OWN failure string": the pinned row and the sixteen ref-a rows are fixed differently, and a
|
||||||
|
# reader who gets only the generic paragraph goes looking for a diff against <ref-a> that does
|
||||||
|
# not exist.
|
||||||
|
rm -rf "$WORK_DIR/pinperturbed"
|
||||||
|
cp -r "$WORK_DIR/pristine" "$WORK_DIR/pinperturbed" || exit 2
|
||||||
|
write_spec "$WORK_DIR/pinperturbed" "$WORK_DIR/pinperturbed.spec"
|
||||||
|
python3 "$PY" perturb "$WORK_DIR/pinperturbed.spec" "$target" \
|
||||||
|
"$WORK_DIR/pristine/$(blob_name "$targetSource")" \
|
||||||
|
"$WORK_DIR/pinperturbed/$(blob_name "$targetSource")" token || exit 2
|
||||||
|
python3 "$PY" extract "$WORK_DIR/pinperturbed.spec" > "$WORK_DIR/pinperturbed.sha" || exit 2
|
||||||
|
cp -f "$WORK_DIR/pristine.sha" "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
|
apply_pinned_shas "$WORK_DIR/pinbase.sha" || exit 2
|
||||||
|
if compare_lists "$WORK_DIR/pinbase.sha" "$WORK_DIR/pinperturbed.sha" \
|
||||||
|
"PIN($PINNED_BASELINE_REF)" "one-token-perturbed" 2> "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL DID NOT TRIP: one token was inserted into $target's body and the"
|
||||||
|
say "comparison AGAINST THE PIN still reported every region as identical. The pinned row is"
|
||||||
|
say "not being compared at all, so every green this gate has printed for it means nothing."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! grep -q "FIRST REGION THAT MOVED: $target" "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: the comparison against the pin went red"
|
||||||
|
say "but did not name $target as the first region that moved. It said:"
|
||||||
|
sed 's/^/[p4a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if ! grep -q "$target IS A PINNED ROW" "$WORK_DIR/pinperturbed.err"; then
|
||||||
|
say "NEGATIVE CONTROL TRIPPED FOR THE WRONG REASON: it went red and named $target, but did"
|
||||||
|
say "not say that this row's baseline is the PIN. That is the half a reader acts on, and a"
|
||||||
|
say "control that does not assert its own message is not a control (R-16). It said:"
|
||||||
|
sed 's/^/[p4a-untouched] /' "$WORK_DIR/pinperturbed.err" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
controls=$((controls + 1))
|
||||||
|
say "negative control $controls: a ONE-TOKEN edit to the pinned $target body goes red AGAINST"
|
||||||
|
say " THE PIN, is named, and says the row is pinned"
|
||||||
|
|
||||||
|
# NOT a failure, deliberately: --self-test is about whether the comparison works, and it runs
|
||||||
|
# on the WORKING tree, which may legitimately carry an uncommitted edit. The two-ref gate is
|
||||||
|
# what fails when the committed region has left the pin.
|
||||||
|
treeSha=$(awk -v n="$target" '$2 == n { print $1 }' "$WORK_DIR/pristine.sha")
|
||||||
|
if [ "$treeSha" != "$pinned" ]; then
|
||||||
|
say "NOTE: this working tree's $target hashes $treeSha, not the pin ($pinned). The"
|
||||||
|
say " self-test's verdict is unaffected; the two-ref gate will be RED until you re-pin or"
|
||||||
|
say " revert."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
say "self-test passed: $controls negative controls, all tripped and all named"
|
say "self-test passed: $controls negative controls, all tripped and all named"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user