mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Refactor, Test] (docs/ci, docs/Disaggregated, MG_Backend/MGPipe, MG_Impl/Pipe, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Pipe, MG_Test/Pipe, MobileGL, scripts/data, tools/trace_replay): delete the MOBILEGL_PIPE_POISON_OMIT knob
- the PoisonOmitted and VerifySplitPoisonOmitted entries set the harness marker MGITEST_POISON_OMIT; the re-executed child arms the pair through Harness/PoisonOmissionArm.h - MGPipeSetPoisonOmission, the stamp withhold, the name lookups and the server half (now one atomic word) compile only with MOBILEGL_PIPE_VERIFY - three PipeInputsTest cases withhold the stamp test-side; PoisonOmitKnobArmsTheOmission and BadPoisonOmitKnobIsFatalNamingTheKnob are retired - skip census: the two retired cases leave the unit-strict and unit-verify names
This commit is contained in:
@@ -469,12 +469,6 @@ namespace MobileGL::MG_Config {
|
||||
// negative control, MOBILEGL_PIPE_VERIFY_CORRUPT, has no field here: only a verify
|
||||
// build reads it, when the comparator arms (MG_Impl/Pipe/PipeFill.cpp, ArmVerify).
|
||||
Bool PipeVerifyFatal = true;
|
||||
// MOBILEGL_PIPE_POISON_OMIT: <Verb>:<FieldName>; the filler skips the STAMP (not
|
||||
// the value) of that field for that verb, an omission indistinguishable from a
|
||||
// forgotten FillPoints.def row, so that verb's read of it is
|
||||
// Fatal{UnmigratedPipeInput} (negative control B). Unknown name is
|
||||
// Fatal{PipeVerifyBadKnob}.
|
||||
String PipePoisonOmit;
|
||||
// MOBILEGL_PIPE_STATS: dump the boundary counters (bytes, calls, roundtrips,
|
||||
// texture pulls, upload shapes, residual-block bytes, index mirror bytes).
|
||||
Bool PipeStats = false;
|
||||
|
||||
@@ -452,7 +452,6 @@ namespace MobileGL::MG_ConfigLoader {
|
||||
// Defaults ON: read as a tri-state so only an explicitly falsy value turns it off.
|
||||
features.PipeVerifyFatal =
|
||||
QueryEnvQuirkOverride("MOBILEGL_PIPE_VERIFY_FATAL") != MG_Config::QuirkOverride::ForceOff;
|
||||
QueryEnvVariable("MOBILEGL_PIPE_POISON_OMIT", features.PipePoisonOmit, "");
|
||||
features.PipeStats = QueryEnvFlag("MOBILEGL_PIPE_STATS");
|
||||
features.PipeIndexMirrorMb = QueryEnvUint32("MOBILEGL_PIPE_INDEX_MIRROR_MB", 64, 0, 4096);
|
||||
features.PipeStatsPeriod = QueryEnvUint32("MOBILEGL_PIPE_STATS_PERIOD", 120, 1, 1000000);
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// The backend-side half of the PipeInputs block: the poison Fatal with its verb name, the
|
||||
// name lookups the runtime knobs need, and - in a verify build - the per-field equality,
|
||||
// the entry comparator and the corruption injector. Compiled only under MOBILEGL_PIPE_PUSH
|
||||
// (CMakeLists.txt appends it to SOURCE_FILES there), so the pull build never sees it. Spells
|
||||
// no MG_State global: everything that reads the live context lives in MG_Impl/Pipe/PipeFill.cpp.
|
||||
// The backend-side half of the PipeInputs block: the poison Fatal with its verb name and -
|
||||
// in a verify build - the per-field equality, the entry comparator, the corruption injector,
|
||||
// the name lookups its knob and the poison omission need, and the omission's server half.
|
||||
// Spells no MG_State global: everything that reads the live context lives in
|
||||
// MG_Impl/Pipe/PipeFill.cpp.
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
@@ -29,22 +30,6 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeInputPoisonFatal(field, MGPipeVerbName(verb));
|
||||
}
|
||||
|
||||
Optional<MGPipeInputField> MGPipeFindInputField(const char* name) {
|
||||
if (name == nullptr) return std::nullopt;
|
||||
for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) {
|
||||
if (std::strcmp(kMGPipeInputFieldNames[i], name) == 0) return static_cast<MGPipeInputField>(i);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Optional<MGPipeVerb> MGPipeFindVerb(const char* name) {
|
||||
if (name == nullptr) return std::nullopt;
|
||||
for (SizeT i = 0; i < kMGPipeVerbCount; ++i) {
|
||||
if (std::strcmp(kMGPipeVerbNames[i], name) == 0) return static_cast<MGPipeVerb>(i);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// The stamp is the poison's (m_filled, the generations): a build without MOBILEGL_PIPE_POISON
|
||||
// compiles no read check that could ask any of this. Every transport build has the poison.
|
||||
#if MOBILEGL_PIPE_POISON
|
||||
@@ -55,46 +40,24 @@ namespace MobileGL::MG_Pipe {
|
||||
Uint64 g_residualPulls = 0;
|
||||
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
// ---- MOBILEGL_PIPE_POISON_OMIT, server half ----
|
||||
// ---- negative control B, server half (MGPipeServerSetPoisonOmission) ----
|
||||
//
|
||||
// The knob withholds the stamp of one (verb, field) pair, a negative control for the
|
||||
// The omission withholds the stamp of one (verb, field) pair, a negative control for the
|
||||
// poison check. Under a transport the backend reads the server's stamp, which re-stamps
|
||||
// every field the verb's class can answer, so the server must withhold the pair too or
|
||||
// the control cannot go red. Parsed here because this file is in the server library and
|
||||
// MG_Impl is not; a malformed value is the client's Fatal{PipeVerifyBadKnob}, and this
|
||||
// side simply does not arm. Verify builds only, so the shipped stamp stays branch-free.
|
||||
struct ServerPoisonOmission {
|
||||
Bool Parsed = false;
|
||||
String Value;
|
||||
Bool Armed = false;
|
||||
MGPipeVerb Verb = MGPipeVerb::kVerbCount;
|
||||
MGPipeInputField Field = MGPipeInputField::kFieldCount;
|
||||
};
|
||||
ServerPoisonOmission g_serverOmission;
|
||||
// the control cannot go red. The harness arms it while the apply thread is already
|
||||
// running, so the pair is ONE atomic word: the verb in the high half, the field in the
|
||||
// low one, and no verb at all (0xffff) until something is armed. Verify builds only, so
|
||||
// the shipped stamp stays branch-free.
|
||||
constexpr Uint32 kNoServerOmission = ~Uint32{0};
|
||||
std::atomic<Uint32> g_serverOmission{kNoServerOmission};
|
||||
|
||||
const ServerPoisonOmission& ServerOmission() {
|
||||
const String& knob = MG_Config::Features.PipePoisonOmit;
|
||||
if (g_serverOmission.Parsed && knob.size() == g_serverOmission.Value.size() &&
|
||||
(knob.empty() || knob == g_serverOmission.Value)) {
|
||||
return g_serverOmission;
|
||||
}
|
||||
g_serverOmission = ServerPoisonOmission{};
|
||||
g_serverOmission.Parsed = true;
|
||||
g_serverOmission.Value = knob;
|
||||
const auto colon = knob.find(':');
|
||||
if (colon == String::npos || colon == 0 || colon + 1 >= knob.size()) return g_serverOmission;
|
||||
const auto verb = MGPipeFindVerb(knob.substr(0, colon).c_str());
|
||||
const auto field = MGPipeFindInputField(knob.substr(colon + 1).c_str());
|
||||
if (!verb || !field) return g_serverOmission;
|
||||
g_serverOmission.Armed = true;
|
||||
g_serverOmission.Verb = *verb;
|
||||
g_serverOmission.Field = *field;
|
||||
return g_serverOmission;
|
||||
constexpr Uint32 PackServerOmission(MGPipeVerb verb, MGPipeInputField field) {
|
||||
return (static_cast<Uint32>(verb) << 16) | static_cast<Uint32>(field);
|
||||
}
|
||||
|
||||
Bool ServerOmitsStamp(MGPipeVerb verb, MGPipeInputField field) {
|
||||
const ServerPoisonOmission& omission = ServerOmission();
|
||||
return omission.Armed && omission.Verb == verb && omission.Field == field;
|
||||
return g_serverOmission.load(std::memory_order_acquire) == PackServerOmission(verb, field);
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_VERIFY
|
||||
|
||||
@@ -453,12 +416,10 @@ namespace MobileGL::MG_Pipe {
|
||||
filled.FilledGen[i] = serial & (Uint64{0} - bit);
|
||||
}
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
// Negative control B's server half (ServerPoisonOmission above): the one withheld pair.
|
||||
// Negative control B's server half (g_serverOmission above): the one withheld pair.
|
||||
{
|
||||
const ServerPoisonOmission& omission = ServerOmission();
|
||||
if (omission.Armed && omission.Verb == verb) {
|
||||
filled.FilledGen[static_cast<SizeT>(omission.Field)] = 0;
|
||||
}
|
||||
const Uint32 omission = g_serverOmission.load(std::memory_order_acquire);
|
||||
if ((omission >> 16) == static_cast<Uint32>(verb)) filled.FilledGen[omission & 0xffffu] = 0;
|
||||
}
|
||||
#endif
|
||||
MGPipeStampAccess::SetServerStamped(inputs, true);
|
||||
@@ -475,6 +436,12 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeStampAccess::SetServerStamped(gPipeInputs, false);
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
void MGPipeServerSetPoisonOmission(MGPipeVerb verb, MGPipeInputField field) {
|
||||
g_serverOmission.store(PackServerOmission(verb, field), std::memory_order_release);
|
||||
}
|
||||
#endif
|
||||
|
||||
Uint64 MGPipeResidualPullCount() {
|
||||
return g_residualPulls;
|
||||
}
|
||||
@@ -691,5 +658,21 @@ namespace MobileGL::MG_Pipe {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
Optional<MGPipeInputField> MGPipeFindInputField(const char* name) {
|
||||
if (name == nullptr) return std::nullopt;
|
||||
for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) {
|
||||
if (std::strcmp(kMGPipeInputFieldNames[i], name) == 0) return static_cast<MGPipeInputField>(i);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Optional<MGPipeVerb> MGPipeFindVerb(const char* name) {
|
||||
if (name == nullptr) return std::nullopt;
|
||||
for (SizeT i = 0; i < kMGPipeVerbCount; ++i) {
|
||||
if (std::strcmp(kMGPipeVerbNames[i], name) == 0) return static_cast<MGPipeVerb>(i);
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif // MOBILEGL_PIPE_VERIFY
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
|
||||
@@ -46,10 +46,6 @@ namespace MobileGL::MG_Pipe {
|
||||
[[noreturn]] void MGPipeInputPoisonFatalForVerb(MGPipeInputField field, MGPipeVerb verb);
|
||||
// kMGPipeVerbNames[verb], or "<none>" for kVerbCount (no verb has been filled yet).
|
||||
const char* MGPipeVerbName(MGPipeVerb verb);
|
||||
// Name lookups for the runtime knobs (MOBILEGL_PIPE_VERIFY_CORRUPT names a field,
|
||||
// MOBILEGL_PIPE_POISON_OMIT a Verb:Field pair). Empty on an unknown name.
|
||||
Optional<MGPipeInputField> MGPipeFindInputField(const char* name);
|
||||
Optional<MGPipeVerb> MGPipeFindVerb(const char* name);
|
||||
|
||||
// The server-side read check (ARCHITECTURE.md §9.2.1). Inside a server-stamped verb
|
||||
// (PipeInputs::ServerStampedVerb) a stale read is judged by the field's ownership class:
|
||||
@@ -996,6 +992,13 @@ namespace MobileGL::MG_Pipe {
|
||||
// dereferenced, the snapshot is only ever compared). Returns false for a forwarded field,
|
||||
// which has nothing to corrupt.
|
||||
Bool MGPipeApplyVerifyCorruption(PipeInputs& snapshot, MGPipeInputField field);
|
||||
// PipeInputs.cpp. Name lookups for MOBILEGL_PIPE_VERIFY_CORRUPT (a field) and
|
||||
// MGPipeSetPoisonOmission (a verb and a field). Empty on an unknown name.
|
||||
Optional<MGPipeInputField> MGPipeFindInputField(const char* name);
|
||||
Optional<MGPipeVerb> MGPipeFindVerb(const char* name);
|
||||
// PipeInputs.cpp. Negative control B's server half, armed by MGPipeSetPoisonOmission: the
|
||||
// server's verb stamp withholds the stamp of `field` at `verb` too.
|
||||
void MGPipeServerSetPoisonOmission(MGPipeVerb verb, MGPipeInputField field);
|
||||
// PipeFill.cpp. Writes the MOBILEGL_PIPE_VERIFY_FATAL=0 summary line ("N divergence(s)
|
||||
// survived") NOW, while the role's log file is still open. MobileGL::Destroy calls it right
|
||||
// before MG_Util::Debug::Close(): the line used to come from a namespace-scope static's
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
// The client side of the PipeInputs block (ARCHITECTURE.md 9.2 phase A): the only place in
|
||||
// the push arm that reads MG_State::pGLContext. Holds the per-verb filler, the F-class
|
||||
// forwarders, IsLive, the MOBILEGL_PIPE_POISON_OMIT knob and - in a verify build - the
|
||||
// second arm (SnapshotFromGLContext), the entry compare, the compare-at-read hook and the
|
||||
// MOBILEGL_PIPE_VERIFY_CORRUPT knob, which only this build reads.
|
||||
// forwarders, IsLive and - in a verify build - the second arm (SnapshotFromGLContext), the
|
||||
// entry compare, the compare-at-read hook, the MOBILEGL_PIPE_VERIFY_CORRUPT knob, which only
|
||||
// this build reads, and the poison omission (MGPipeSetPoisonOmission).
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/BufferState/BufferState.h>
|
||||
// C-1: the vertex-elements CSO's death path raises the backend notice from here, between the
|
||||
@@ -457,62 +457,40 @@ namespace MobileGL::MG_Pipe {
|
||||
return null;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
[[noreturn]] void BadKnob(const char* knob, const char* value, const char* why) {
|
||||
MGLOG_F("MGPipe: Fatal{PipeVerifyBadKnob, \"%s=%s\": %s}", knob, value, why);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---- MOBILEGL_PIPE_POISON_OMIT (negative control B, P1 brief D6) ----
|
||||
// ---- negative control B (P1 brief D6), armed by MGPipeSetPoisonOmission ----
|
||||
// The filler skips the STAMP (never the value) of one (verb, field) pair: an omission
|
||||
// indistinguishable from a forgotten FillPoints.def row, so that verb's read of the
|
||||
// field is Fatal{UnmigratedPipeInput, "Field@Verb"} and no other verb is affected.
|
||||
// field is Fatal{UnmigratedPipeInput, "Field@Verb"} and no other verb is affected. The
|
||||
// GL thread arms it and the GL thread's fill reads it; the server's stamp has its own.
|
||||
struct PoisonOmission {
|
||||
Bool Armed = false;
|
||||
MGPipeVerb Verb = MGPipeVerb::kVerbCount;
|
||||
MGPipeInputField Field = MGPipeInputField::kFieldCount;
|
||||
};
|
||||
PoisonOmission g_omission;
|
||||
Bool g_omissionKnobParsed = false;
|
||||
String g_omissionKnobValue; // the value the last parse saw
|
||||
|
||||
// Parsed on the first fill and again only when the value changes. A lane loads
|
||||
// Features once, before any fill, so that is one parse per process there; a forked
|
||||
// test child that sets Features after its parent already filled gets its own parse,
|
||||
// which is what puts the parser and its Fatal{PipeVerifyBadKnob} under a unit test.
|
||||
// An empty value never clears an omission a test armed through MGPipeSetPoisonOmission.
|
||||
void ParsePoisonOmissionKnob() {
|
||||
const String& knob = MG_Config::Features.PipePoisonOmit;
|
||||
// THE UNSET KNOB IS TWO LENGTH LOADS AND A BRANCH, inline, and that is the whole
|
||||
// change (P5d r3, package C): this runs at every verb - 852 draws a frame on the
|
||||
// profiled workload - and `knob == g_omissionKnobValue` is an out-of-line String
|
||||
// compare even when both sides are empty, which is every shipping configuration.
|
||||
// Sizes first, contents only when a non-empty value is involved; the re-parse
|
||||
// condition is unchanged (the value differing from what the last parse latched).
|
||||
if (g_omissionKnobParsed && knob.size() == g_omissionKnobValue.size() &&
|
||||
(knob.empty() || knob == g_omissionKnobValue)) {
|
||||
return;
|
||||
// The stamp the fill's walk is about to overwrite for the omitted pair, put back after the
|
||||
// branch-free stamp of the whole walk: the value is copied either way, only the stamp is
|
||||
// withheld. Index kMGPipeInputFieldCount is "nothing withheld at this verb".
|
||||
struct WithheldStamp {
|
||||
SizeT Index = kMGPipeInputFieldCount;
|
||||
Uint64 Gen = 0;
|
||||
void PutBack(MGPipeFilledState& filled) const {
|
||||
if (Index < kMGPipeInputFieldCount) filled.FilledGen[Index] = Gen;
|
||||
}
|
||||
g_omissionKnobParsed = true;
|
||||
g_omissionKnobValue = knob;
|
||||
if (knob.empty()) return;
|
||||
const auto colon = knob.find(':');
|
||||
if (colon == String::npos || colon == 0 || colon + 1 >= knob.size()) {
|
||||
BadKnob("MOBILEGL_PIPE_POISON_OMIT", knob.c_str(), "expected <Verb>:<FieldName>");
|
||||
}
|
||||
const String verbName = knob.substr(0, colon);
|
||||
const String fieldName = knob.substr(colon + 1);
|
||||
const auto verb = MGPipeFindVerb(verbName.c_str());
|
||||
if (!verb) BadKnob("MOBILEGL_PIPE_POISON_OMIT", knob.c_str(), "no such verb in kMGPipeVerbNames");
|
||||
const auto field = MGPipeFindInputField(fieldName.c_str());
|
||||
if (!field) BadKnob("MOBILEGL_PIPE_POISON_OMIT", knob.c_str(), "no such field in kMGPipeInputFieldNames");
|
||||
MGPipeSetPoisonOmission(verbName.c_str(), fieldName.c_str());
|
||||
};
|
||||
WithheldStamp StampToWithhold(const MGPipeFilledState& filled, MGPipeVerb verb) {
|
||||
if (!g_omission.Armed || g_omission.Verb != verb) return {};
|
||||
const auto index = static_cast<SizeT>(g_omission.Field);
|
||||
return {index, filled.FilledGen[index]};
|
||||
}
|
||||
|
||||
[[maybe_unused]] Bool IsOmitted(MGPipeVerb verb, MGPipeInputField field) {
|
||||
return g_omission.Armed && g_omission.Verb == verb && g_omission.Field == field;
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
// ---- the MOBILEGL_PIPE_VERIFY comparator (P1 brief D8) ----
|
||||
// Two mechanisms, both active only when Features.PipeVerify is set: the ENTRY compare
|
||||
// once per verb (the pushed block against a second snapshot of the live context,
|
||||
@@ -1906,25 +1884,17 @@ namespace MobileGL::MG_Pipe {
|
||||
return published;
|
||||
}
|
||||
|
||||
void MGPipeSetPoisonOmission(const char* verb, const char* field) {
|
||||
if (verb == nullptr || field == nullptr) {
|
||||
g_omission = PoisonOmission{};
|
||||
return;
|
||||
}
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
Bool MGPipeSetPoisonOmission(const char* verb, const char* field) {
|
||||
const auto v = MGPipeFindVerb(verb);
|
||||
const auto f = MGPipeFindInputField(field);
|
||||
if (!v || !f) BadKnob("MOBILEGL_PIPE_POISON_OMIT", verb, "unknown verb or field");
|
||||
g_omission.Armed = true;
|
||||
g_omission.Verb = *v;
|
||||
g_omission.Field = *f;
|
||||
#if MOBILEGL_PIPE_POISON
|
||||
if (!v || !f) return false;
|
||||
g_omission = PoisonOmission{true, *v, *f};
|
||||
MGPipeServerSetPoisonOmission(*v, *f);
|
||||
MGLOG_I("MGPipe: poison omission armed - %s@%s", field, verb);
|
||||
#else
|
||||
MGLOG_W_ONCE("MGPipe: poison omission %s@%s requested but the poison is not compiled in "
|
||||
"(MOBILEGL_PIPE_POISON=0): no stamp exists to omit",
|
||||
field, verb);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---- liveness ----
|
||||
Bool PipeInputs::IsLive() const {
|
||||
@@ -3039,7 +3009,6 @@ namespace MobileGL::MG_Pipe {
|
||||
// every verb) and the guard below then fires Fatal{RoleViolation, "gPipeInputs"} on
|
||||
// the first unbarriered verb, by name.
|
||||
const Bool runAhead = ClientRunsAhead();
|
||||
ParsePoisonOmissionKnob();
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
ArmVerify();
|
||||
#else
|
||||
@@ -3390,18 +3359,19 @@ namespace MobileGL::MG_Pipe {
|
||||
MGPipeFillAccess::CopyField(inputs, *ctx, static_cast<MGPipeInputField>(copies[k]));
|
||||
}
|
||||
#if MOBILEGL_PIPE_POISON
|
||||
// The value is copied either way; only the stamp is withheld for the omitted pair,
|
||||
// which is put back to what it held after the branch-free stamp of the whole walk.
|
||||
const Uint8* const walk = g_fillPlan.Walk[planClass].data();
|
||||
const SizeT walkCount = g_fillPlan.WalkCount[planClass];
|
||||
const Uint64 serial = filled.CurrentVerbSerial;
|
||||
const Bool omit = g_omission.Armed && g_omission.Verb == verb;
|
||||
const SizeT omitted = omit ? static_cast<SizeT>(g_omission.Field) : 0;
|
||||
const Uint64 omittedGen = omit ? filled.FilledGen[omitted] : 0;
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
// Negative control B: the omitted pair's stamp goes back after the walk (WithheldStamp).
|
||||
const WithheldStamp withheld = StampToWithhold(filled, verb);
|
||||
#endif
|
||||
for (SizeT k = 0; k < walkCount; ++k) {
|
||||
filled.FilledGen[walk[k] & ~ResidualFillPlan::kCopy] = serial;
|
||||
}
|
||||
if (omit) filled.FilledGen[omitted] = omittedGen;
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
withheld.PutBack(filled);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
if (payloadBytes != 0 && MG_Util::PipeStats::Enabled()) {
|
||||
|
||||
@@ -81,14 +81,6 @@ namespace MobileGL::MG_Pipe {
|
||||
// (TrackerWalk.ABaseInstanceSurvivesTheFirstWalkOnAFreshContext).
|
||||
Uint32 MGPipePendingBaseInstance();
|
||||
|
||||
// PipeFill.cpp. Negative control B (P1 brief D6): the filler withholds the STAMP - never
|
||||
// the value - of `field` at `verb`, so that verb's read of it is
|
||||
// Fatal{UnmigratedPipeInput, "Field@Verb"} while every other verb is unaffected. The
|
||||
// MOBILEGL_PIPE_POISON_OMIT knob ("<Verb>:<FieldName>") calls this once, on the first
|
||||
// fill; tests call it directly. Both null clears the omission. An unknown name is
|
||||
// Fatal{PipeVerifyBadKnob}.
|
||||
void MGPipeSetPoisonOmission(const char* verb, const char* field);
|
||||
|
||||
// PipeFill.cpp. How many times set_vertex_attrib_defaults' applier failed to reproduce
|
||||
// the value the call carried, so the client wrote the mirror itself
|
||||
// (EmitVertexAttribDefaults). It is the ONE observable of that repair: the window it
|
||||
@@ -119,6 +111,13 @@ namespace MobileGL::MG_Pipe {
|
||||
// is the branch that survives P13, which is why it is its own function rather than the
|
||||
// filler's loop.
|
||||
void SnapshotFromGLContext(PipeInputs& snapshot, const MGPipeFieldMask& mask);
|
||||
|
||||
// PipeFill.cpp. Negative control B (P1 brief D6): from now on the filler withholds the STAMP -
|
||||
// never the value - of `field` at `verb`, and so does the server's verb stamp, so that verb's
|
||||
// read of it is Fatal{UnmigratedPipeInput, "Field@Verb"} while every other verb is unaffected.
|
||||
// Names from kMGPipeVerbNames / kMGPipeInputFieldNames; false, arming nothing, for an unknown
|
||||
// one. Call it on the GL thread. The integration harness arms it (Harness/PoisonOmissionArm.h).
|
||||
Bool MGPipeSetPoisonOmission(const char* verb, const char* field);
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Pipe
|
||||
#define MGP_FILL(Verb) ::MobileGL::MG_Pipe::MGPipeValidateForVerb(::MobileGL::MG_Pipe::MGPipeVerb::Verb)
|
||||
|
||||
@@ -56,6 +56,7 @@ add_executable(MobileGLIntegrationTest
|
||||
Harness/P4aSeamPeek.cpp
|
||||
Harness/P4aFinalFixPeek.cpp
|
||||
Harness/PersistentMapPeek.cpp
|
||||
Harness/PoisonOmissionArm.cpp
|
||||
Harness/SplitRuntimePeek.cpp
|
||||
# ---- P3b/P4b wave 2-D package D2 (own block; D1 edits this file elsewhere) --------------
|
||||
# The client texture emitter's own counters, for TextureViewAliasScenario's half of the
|
||||
@@ -1309,7 +1310,9 @@ if (MOBILEGL_PIPE_VERIFY)
|
||||
|
||||
# Negative control B (G5). The omission skips the STAMP of one field for one verb while still
|
||||
# copying its value, which is indistinguishable from a fill row nobody wrote; the scenario
|
||||
# forks, so the resulting std::abort() is a datum in waitpid() rather than a dead lane.
|
||||
# forks, so the resulting std::abort() is a datum in waitpid() rather than a dead lane. The
|
||||
# pair is a harness marker, MGITEST_POISON_OMIT: the library has no knob for it, and the
|
||||
# re-executed child arms it through the verify build's setter (Harness/PoisonOmissionArm.h).
|
||||
# P13 W5: the pair is the pack state at ReadPixels, which monolith's record arm still reads from
|
||||
# the fill (around its neutral-pack readback). GetActiveTextureUnit at GenerateMipmap was the
|
||||
# frontend arm's read; the record arm resolves the texture from the verb's own handle and never
|
||||
@@ -1317,13 +1320,13 @@ if (MOBILEGL_PIPE_VERIFY)
|
||||
mgl_itest_join_environment(MGL_ITEST_GLES_POISON_OMIT_ENVIRONMENT
|
||||
"MOBILEGL_TRANSPORT=monolith"
|
||||
"MOBILEGL_BACKEND_TYPE=DirectGLES" "MOBILEGL_PIPE_VERIFY=1"
|
||||
"MOBILEGL_PIPE_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
|
||||
"MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
|
||||
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectGLES.log"
|
||||
${MGL_ITEST_COMMON_ENV})
|
||||
mgl_itest_join_environment(MGL_ITEST_VULKAN_POISON_OMIT_ENVIRONMENT
|
||||
"MOBILEGL_TRANSPORT=monolith"
|
||||
"MOBILEGL_BACKEND_TYPE=DirectVulkan" "MOBILEGL_PIPE_VERIFY=1"
|
||||
"MOBILEGL_PIPE_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
|
||||
"MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters"
|
||||
"MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-DirectVulkan.log"
|
||||
${MGL_ITEST_VULKAN_ENV})
|
||||
|
||||
@@ -3663,15 +3666,13 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND MOBILEGL_PIPE_VERIFY)
|
||||
# PoisonOmissionScenario's ReadChildLog reads BOTH role halves of the child's log, because
|
||||
# under a transport the poisoned read can fire on either side of the seam.
|
||||
#
|
||||
# A DIFFERENT PAIR FROM THE MONOLITH ENTRIES', and the monolith pair is not "also fine
|
||||
# here": GenerateMipmap:GetActiveTextureUnit is inert on this arm - the split backend's
|
||||
# generate_mipmap resolves its texture from the verb's handle and never reads the active
|
||||
# unit, so the child exited 0 with that stamp withheld (measured, both backends).
|
||||
# ReadPixels:GetPixelStoreParameters is read on the server under the server's stamp,
|
||||
# which is the stamp the knob has to withhold on this arm (PipeInputs.cpp's
|
||||
# ServerPoisonOmission). PoisonOmissionScenario.cpp's kOmittedPairs carries both.
|
||||
# THE MONOLITH ENTRIES' PAIR. ReadPixels:GetPixelStoreParameters is read on the server under
|
||||
# the server's stamp, which is the stamp the omission has to withhold on this arm
|
||||
# (PipeInputs.cpp's MGPipeServerSetPoisonOmission). GenerateMipmap:GetActiveTextureUnit is
|
||||
# inert here: generate_mipmap resolves its texture from the verb's handle and never reads
|
||||
# the active unit, so the child exited 0 with that stamp withheld (measured, both backends).
|
||||
mgl_itest_lane("${mglItestVerifyBackend}.VerifySplitPoisonOmitted." "PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb"
|
||||
LABELS "integration-verify-split" TIMEOUT ${MGL_ITEST_VERIFY_TIMEOUT} LOCK "pipe-poison-omit-split-${mglItestVerifyBackend}.log" ENV "${mglItestVerifyArmEnv}\;MOBILEGL_PIPE_POISON_OMIT=ReadPixels:GetPixelStoreParameters\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-split-${mglItestVerifyBackend}.log")
|
||||
LABELS "integration-verify-split" TIMEOUT ${MGL_ITEST_VERIFY_TIMEOUT} LOCK "pipe-poison-omit-split-${mglItestVerifyBackend}.log" ENV "${mglItestVerifyArmEnv}\;MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/pipe-poison-omit-split-${mglItestVerifyBackend}.log")
|
||||
# B's AMBIENT HALF. `Verify.PoisonOmissionScenario.WithoutOmissionCompletes` runs the
|
||||
# scenario with nothing withheld, and the mechanism filter took it out of the ambient set
|
||||
# above along with the rest of the scenario. Registered back by hand, with no knob, so
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/PoisonOmissionArm.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "PoisonOmissionArm.h"
|
||||
|
||||
#if !defined(__ANDROID__)
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
#include <MG_Impl/Pipe/PipeFill.h>
|
||||
#if MOBILEGL_PIPE_VERIFY
|
||||
#define MGITEST_POISON_OMISSION_ARM_LIVE 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
#if defined(MGITEST_POISON_OMISSION_ARM_LIVE)
|
||||
bool PoisonOmissionArmAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArmPoisonOmission(const char* verb, const char* field) {
|
||||
return MobileGL::MG_Pipe::MGPipeSetPoisonOmission(verb, field);
|
||||
}
|
||||
#else
|
||||
bool PoisonOmissionArmAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ArmPoisonOmission(const char*, const char*) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -0,0 +1,34 @@
|
||||
// MobileGL - MobileGL/MG_IntegrationTest/Harness/PoisonOmissionArm.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
//
|
||||
// NEGATIVE CONTROL B'S OMISSION, ARMED FROM THE HARNESS. PoisonOmissionScenario's re-executed
|
||||
// child withholds the stamp of one (verb, field) pair before it runs the sequence, so that verb's
|
||||
// read of the field must be Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}. The library has no knob
|
||||
// for it: only a -DMOBILEGL_PIPE_VERIFY=ON build compiles the setter (MG_Impl/Pipe/PipeFill.h,
|
||||
// MGPipeSetPoisonOmission), and this file is the one place that calls it.
|
||||
//
|
||||
// A SEPARATE TRANSLATION UNIT for PipeApplyPeek's reason, verbatim: the scenario sources include
|
||||
// the GL headers with prototypes and the pipe headers are not meant to meet them in one file.
|
||||
//
|
||||
// Both entry points return false, touching nothing, where the setter is out of reach: a build
|
||||
// without MOBILEGL_PIPE_VERIFY, or Android, where this module links the shipping libMobileGL.so
|
||||
// built -fvisibility=hidden.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace MGITest {
|
||||
|
||||
// True when this build can arm the omission at all.
|
||||
bool PoisonOmissionArmAvailable();
|
||||
|
||||
// Withholds the stamp of `field` at `verb` (kMGPipeVerbNames / kMGPipeInputFieldNames spellings)
|
||||
// for the rest of this process, in the client's fill and in the server's verb stamp. Call it on
|
||||
// the thread that issues the GL calls. False when the build cannot, or a name is unknown.
|
||||
bool ArmPoisonOmission(const char* verb, const char* field);
|
||||
|
||||
} // namespace MGITest
|
||||
@@ -11,22 +11,26 @@
|
||||
// The per-verb poison is the half of P1 that makes a forgotten fill row loud instead of silent: the
|
||||
// filler stamps a generation on every field it copies for a verb, and an accessor whose stamp is not
|
||||
// this verb's aborts with Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}. A mechanism that can only be
|
||||
// observed when someone forgets a row is a mechanism nobody can trust, so MOBILEGL_PIPE_POISON_OMIT
|
||||
// forges the mistake on purpose: it names one (verb, field) pair whose STAMP the filler skips while
|
||||
// still copying the value, which is indistinguishable from a row that was never written.
|
||||
// observed when someone forgets a row is a mechanism nobody can trust, so the control forges the
|
||||
// mistake on purpose: the harness marker MGITEST_POISON_OMIT names one (verb, field) pair, and the
|
||||
// child arms it through the verify build's setter (Harness/PoisonOmissionArm.h), so the filler skips
|
||||
// that pair's STAMP while still copying the value - indistinguishable from a row never written.
|
||||
//
|
||||
// The scenario asserts both halves of "on THAT verb, and only there":
|
||||
//
|
||||
// OmittedFieldAbortsOnThatVerb - with MOBILEGL_PIPE_POISON_OMIT=GenerateMipmap:GetActiveTextureUnit,
|
||||
// a draw must still complete (GetActiveTextureUnit is not in kDraw's
|
||||
// mask, and the draw's own fields are stamped normally) and the
|
||||
// following glGenerateMipmap must abort naming exactly that pair.
|
||||
// WithoutOmissionCompletes - the identical sequence with the knob unset runs to completion with
|
||||
// no Fatal at all. Without this half, "it aborted" would say nothing
|
||||
// about WHY: a poison that fired on every verb would look just as red.
|
||||
// OmittedFieldAbortsOnThatVerb - with MGITEST_POISON_OMIT=ReadPixels:GetPixelStoreParameters, the
|
||||
// draw and the glGenerateMipmap must still complete (the stamp is
|
||||
// withheld at ReadPixels alone, and their own fields are stamped
|
||||
// normally) and the following glReadPixels must abort naming
|
||||
// exactly that pair.
|
||||
// WithoutOmissionCompletes - the identical sequence with the marker unset runs to completion
|
||||
// with no Fatal at all. Without this half, "it aborted" would say
|
||||
// nothing about WHY: a poison that fired on every verb would look
|
||||
// just as red.
|
||||
//
|
||||
// The knob is process-wide, so the two cases cannot share a lane: the first runs in the PoisonOmitted.
|
||||
// entries, the second in the ambient Verify. entries (the first skips when the knob is unset).
|
||||
// The omission is process-wide, so the two cases cannot share a lane: the first runs in the
|
||||
// PoisonOmitted. entries, the second in the ambient Verify. entries (the first skips when the marker
|
||||
// is unset).
|
||||
//
|
||||
// WHY THE SEQUENCE RUNS IN A SEPARATE PROCESS, AND WHY THAT PROCESS IS fork()+execve() AND NOT fork()
|
||||
// ALONE. The poison reports with MGLOG_F and then std::abort(), in the middle of a GL command - so the
|
||||
@@ -56,6 +60,7 @@
|
||||
|
||||
#include "../Harness/PipeStatsWindow.h"
|
||||
#include "../Harness/HeadlessGL.h"
|
||||
#include "../Harness/PoisonOmissionArm.h"
|
||||
#include "../Harness/ScenarioFixture.h"
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && __has_include(<sys/wait.h>)
|
||||
@@ -81,28 +86,23 @@ extern char** environ;
|
||||
namespace MGITest {
|
||||
namespace {
|
||||
|
||||
// What the PoisonOmitted. ctest entries name, which CI requires to pass by name. Each pair is
|
||||
// spelled here so the assertion below is about the exact string the poison contracts to
|
||||
// print (ARCHITECTURE.md 9.2: Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}).
|
||||
// What the PoisonOmitted. and VerifySplitPoisonOmitted. ctest entries put in
|
||||
// MGITEST_POISON_OMIT, and CI requires both to pass by name. The pair is spelled here so the
|
||||
// assertion below is about the exact string the poison contracts to print (ARCHITECTURE.md
|
||||
// 9.2: Fatal{UnmigratedPipeInput, "<Field>@<Verb>"}).
|
||||
//
|
||||
// TWO PAIRS SINCE P7 WAVE 3, ONE PER ARM, because the first one is structurally inert on
|
||||
// the split arm. GenerateMipmap:GetActiveTextureUnit is the monolith control: the monolith
|
||||
// backend resolves the texture to mip through the active unit. Under a transport it does
|
||||
// not - since P5c hd the server's generate_mipmap resolves the texture from the verb's own
|
||||
// handle (MGPipeApplier().VerbMipRes) and never reads the active unit - so omitting that
|
||||
// stamp is omitting a stamp nobody reads, and the child exits 0 (measured on
|
||||
// integration-verify-split, both backends). ReadPixels:GetPixelStoreParameters is the
|
||||
// split control: the server's read_pixels backend call DOES read the pack half through the
|
||||
// accessor, under the server's own stamp, which honours the knob in a verify build
|
||||
// (MG_Backend/MGPipe/PipeInputs.cpp's ServerPoisonOmission).
|
||||
// ONE PAIR FOR BOTH ARMS SINCE P13 W5. The record arm resolves glGenerateMipmap's texture
|
||||
// from the verb's own handle and never reads the active unit, so the old monolith pair,
|
||||
// GenerateMipmap:GetActiveTextureUnit, withheld a stamp nobody reads. ReadPixels' pack
|
||||
// state is read on both arms: by the monolith record arm around its neutral-pack readback,
|
||||
// and by the server's read_pixels under the server's own stamp, which withholds the pair
|
||||
// too (MG_Backend/MGPipe/PipeInputs.cpp, MGPipeServerSetPoisonOmission).
|
||||
//
|
||||
// AND EACH PAIR SAYS WHICH ROLE HAS TO FIRE (`FiresOnTheServer`), which is the whole point
|
||||
// of the split pair and was not checked at all until P7 wave 3's V1 fix round. The reader
|
||||
// below concatenates the child's two role halves and the assertion searched the union, so
|
||||
// `VerifySplitPoisonOmitted.` would have been just as green if the stamp the knob withheld
|
||||
// had been the CLIENT's - which is the case it exists to rule out, because the mechanism
|
||||
// under test is MG_Backend/MGPipe/PipeInputs.cpp's server-side stamp honouring the knob.
|
||||
// The monolith pair has one role and one log half, so it keeps the union.
|
||||
// AND THE PAIR SAYS WHICH ROLE HAS TO FIRE (`FiresOnTheServer`, P7 wave 3's V1 fix round).
|
||||
// The reader below concatenates the child's two role halves, so `VerifySplitPoisonOmitted.`
|
||||
// would be just as green if the withheld stamp had been the CLIENT's - the case it exists to
|
||||
// rule out. A build without MOBILEGL_BUILD_DISAGGREGATED has one role and one log half, so
|
||||
// there the union is all there is.
|
||||
struct OmittedPair {
|
||||
const char* Verb;
|
||||
const char* Field;
|
||||
@@ -110,7 +110,6 @@ namespace MGITest {
|
||||
bool FiresOnTheServer; // the Fatal must be in the child's SERVER half, not merely present
|
||||
};
|
||||
constexpr OmittedPair kOmittedPairs[] = {
|
||||
{"GenerateMipmap", "GetActiveTextureUnit", {"@DrawArrays", "@DrawArrays"}, false},
|
||||
{"ReadPixels", "GetPixelStoreParameters", {"@DrawArrays", "@GenerateMipmap"}, true},
|
||||
};
|
||||
constexpr const char* kFatalPrefix = "Fatal{UnmigratedPipeInput";
|
||||
@@ -120,6 +119,11 @@ namespace MGITest {
|
||||
constexpr const char* kChildMarker = "MGITEST_POISON_OMISSION_CHILD";
|
||||
constexpr const char* kWorkerFilter = "--gtest_filter=PoisonOmissionScenario.TheSequenceThePoisonControlsRun";
|
||||
|
||||
// The pair to withhold, as <Verb>:<Field>, set by the PoisonOmitted. ctest entries and
|
||||
// inherited by the child. A HARNESS marker (hence MGITEST_): the library never reads it,
|
||||
// and the worker arms the omission itself (Harness/PoisonOmissionArm.h).
|
||||
constexpr const char* kOmissionMarker = "MGITEST_POISON_OMIT";
|
||||
|
||||
constexpr const char* kVS = R"(#version 330 core
|
||||
in vec2 aPos;
|
||||
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
|
||||
@@ -265,14 +269,14 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
glFinish();
|
||||
std::fprintf(stderr, "[itest] poison worker: the draw completed\n");
|
||||
|
||||
// The verb the omission names. Under MOBILEGL_PIPE_POISON_OMIT this must abort.
|
||||
// A texture verb between the draw and the readback; the omission does not name it.
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glFinish();
|
||||
std::fprintf(stderr, "[itest] poison worker: glGenerateMipmap returned\n");
|
||||
|
||||
// The split arm's verb (kOmittedPairs[1]): a readback, which is a round trip, so
|
||||
// the server has applied it - and read the pack state - before this returns.
|
||||
// The verb the omission names (kOmittedPairs): a readback, which is a round trip,
|
||||
// so the server has applied it - and read the pack state - before this returns.
|
||||
unsigned char centre[4] = {};
|
||||
glReadPixels(gl.Width() / 2, gl.Height() / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, centre);
|
||||
std::fprintf(stderr, "[itest] poison worker: glReadPixels returned\n");
|
||||
@@ -382,6 +386,18 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
}
|
||||
if (!Ready()) return;
|
||||
|
||||
// The omission the marker names, armed on this thread - the one that issues the GL calls
|
||||
// - before the sequence's first verb. Without the marker nothing is armed, which is
|
||||
// WithoutOmissionCompletes' half.
|
||||
if (const char* marker = std::getenv(kOmissionMarker); marker != nullptr && *marker != '\0') {
|
||||
const std::string pair(marker);
|
||||
const std::string::size_type colon = pair.find(':');
|
||||
ASSERT_NE(colon, std::string::npos) << kOmissionMarker << "=" << pair << " is not <Verb>:<Field>";
|
||||
ASSERT_TRUE(ArmPoisonOmission(pair.substr(0, colon).c_str(), pair.substr(colon + 1).c_str()))
|
||||
<< "could not arm the omission " << kOmissionMarker << "=" << pair
|
||||
<< ": the build has no setter (-DMOBILEGL_PIPE_VERIFY=ON compiles it) or a name is unknown";
|
||||
}
|
||||
|
||||
RunSequence();
|
||||
|
||||
#if MGITEST_POISON_HAVE_FORK
|
||||
@@ -404,21 +420,22 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
TEST_F(PoisonOmissionScenario, OmittedFieldAbortsOnThatVerb) {
|
||||
if (!Ready()) return;
|
||||
|
||||
if (!StringKnobIsSet("MOBILEGL_PIPE_POISON_OMIT")) {
|
||||
GTEST_SKIP() << "this case is the poison's negative control and needs "
|
||||
"MOBILEGL_PIPE_POISON_OMIT=<Verb>:<Field> for the whole process, which "
|
||||
"is what the PoisonOmitted. ctest entries set";
|
||||
if (!StringKnobIsSet(kOmissionMarker)) {
|
||||
GTEST_SKIP() << "this case is the poison's negative control and needs " << kOmissionMarker
|
||||
<< "=<Verb>:<Field>, which is what the PoisonOmitted. ctest entries set";
|
||||
}
|
||||
const std::string knob = std::getenv("MOBILEGL_PIPE_POISON_OMIT");
|
||||
const std::string marker = std::getenv(kOmissionMarker);
|
||||
const OmittedPair* pair = nullptr;
|
||||
for (const OmittedPair& candidate : kOmittedPairs) {
|
||||
if (knob == std::string(candidate.Verb) + ":" + candidate.Field) pair = &candidate;
|
||||
if (marker == std::string(candidate.Verb) + ":" + candidate.Field) pair = &candidate;
|
||||
}
|
||||
if (pair == nullptr) {
|
||||
GTEST_SKIP() << "MOBILEGL_PIPE_POISON_OMIT is " << knob << ", but this case only knows "
|
||||
<< "how to provoke " << kOmittedPairs[0].Verb << ":" << kOmittedPairs[0].Field << " and "
|
||||
<< kOmittedPairs[1].Verb << ":" << kOmittedPairs[1].Field;
|
||||
GTEST_SKIP() << kOmissionMarker << " is " << marker << ", but this case only knows how to provoke "
|
||||
<< kOmittedPairs[0].Verb << ":" << kOmittedPairs[0].Field;
|
||||
}
|
||||
ASSERT_TRUE(PoisonOmissionArmAvailable())
|
||||
<< kOmissionMarker << " is set, but only a -DMOBILEGL_PIPE_VERIFY=ON build compiles the setter "
|
||||
<< "the child arms the omission with (Harness/PoisonOmissionArm.h), so this control cannot run here";
|
||||
const std::string expectedPair = std::string(pair->Field) + "@" + pair->Verb;
|
||||
|
||||
int status = 0;
|
||||
@@ -452,10 +469,10 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
<< childLog;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// WHICH ROLE, for the split pair (P7 wave 3, V1). The union above answers "did the
|
||||
// poison fire", which is all the monolith pair can be asked; this pair exists to prove
|
||||
// that the SERVER'S OWN verb stamp honours MOBILEGL_PIPE_POISON_OMIT
|
||||
// (MG_Backend/MGPipe/PipeInputs.cpp's ServerPoisonOmission) - the thing that was NOT
|
||||
// true before wave 3 and made the control silently green. A client-side firing would
|
||||
// poison fire", which is all a one-role build can be asked; this pair exists to prove
|
||||
// that the SERVER'S OWN verb stamp honours the omission
|
||||
// (MG_Backend/MGPipe/PipeInputs.cpp's MGPipeServerSetPoisonOmission) - the thing that was
|
||||
// NOT true before wave 3 and made the control silently green. A client-side firing would
|
||||
// satisfy the union and prove the opposite of what the entry claims, so the half is
|
||||
// named here.
|
||||
if (pair->FiresOnTheServer) {
|
||||
@@ -481,11 +498,11 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
}
|
||||
|
||||
// The ambient half of the pair, in the ambient Verify. lanes: the same sequence with the
|
||||
// knob UNSET must run to completion and log no Fatal at all. The PoisonOmitted. entries are
|
||||
// the armed half (knob set, the verb must abort), and CI requires those entries by name.
|
||||
// marker UNSET must run to completion and log no Fatal at all. The PoisonOmitted. entries are
|
||||
// the armed half (marker set, the verb must abort), and CI requires those entries by name.
|
||||
//
|
||||
// It does not skip when MOBILEGL_PIPE_POISON_OMIT is set: run by hand with the knob
|
||||
// exported, it goes red, and the note in its failure says that is the poison firing.
|
||||
// It does not skip when MGITEST_POISON_OMIT is set: run by hand with the marker exported,
|
||||
// it goes red, and the note in its failure says that is the poison firing.
|
||||
TEST_F(PoisonOmissionScenario, WithoutOmissionCompletes) {
|
||||
if (!Ready()) return;
|
||||
|
||||
@@ -493,7 +510,7 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
GTEST_SKIP() << "the poison is only compiled into the push/verify builds; in an ordinary "
|
||||
"build there is nothing for this control to be a control OF";
|
||||
}
|
||||
const bool omissionArmed = StringKnobIsSet("MOBILEGL_PIPE_POISON_OMIT");
|
||||
const bool omissionArmed = StringKnobIsSet(kOmissionMarker);
|
||||
|
||||
int status = 0;
|
||||
std::string reason;
|
||||
@@ -501,10 +518,10 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); }
|
||||
|
||||
const std::string childLog = ReadChildLog();
|
||||
const std::string note =
|
||||
omissionArmed
|
||||
? std::string(" NOTE: MOBILEGL_PIPE_POISON_OMIT is set in this process, so this failure is "
|
||||
"expected: the poison IS armed, and this entry going red is the proof.")
|
||||
: std::string();
|
||||
omissionArmed ? std::string(" NOTE: ") + kOmissionMarker +
|
||||
" is set in this process, so this failure is expected: the poison IS armed, and "
|
||||
"this entry going red is the proof."
|
||||
: std::string();
|
||||
ASSERT_TRUE(WIFEXITED(status))
|
||||
<< "with no omission armed, a draw followed by glGenerateMipmap must complete; the child "
|
||||
<< DescribeStatus(status)
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
// MGP_FILL_VERB_LIST: every GLFunctionsTable function pointer in declaration order, with its
|
||||
// class (gen_pipe.py refuses any other set). MGP_FILL_FIELD_LIST: the fields a class may read;
|
||||
// sticky fields (MGP_COVERAGE_STICKY_LIST) are implicit. Fix Fatal{UnmigratedPipeInput} by
|
||||
// adding a row, never by making it sticky. A row is removed only on a MOBILEGL_PIPE_POISON_OMIT
|
||||
// run of the full CTS caselist on both devices; a corpus that never reaches a path proves nothing.
|
||||
// adding a row, never by making it sticky. A row is removed only after a poison build without it
|
||||
// ran the full CTS caselist on both devices; a corpus that never reaches a path proves nothing.
|
||||
// gen_pipe.py's block regexes end at a blank line: keep the empty line after each macro.
|
||||
// clang-format off
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// materialising a queued clear, overriding a sampler's filter) would leave the copy stale, so
|
||||
// the mutator calls MGP_NOTE_MUTATION(Field) where it moves the value. The notice refreshes
|
||||
// that one field when it belongs to the verb in flight. It never touches the poison stamp, so a
|
||||
// stamp withheld by MOBILEGL_PIPE_POISON_OMIT stays withheld.
|
||||
// stamp the fill withheld (negative control B) stays withheld.
|
||||
#include <MG_Pipe/MGPipe.h>
|
||||
namespace MobileGL::MG_Pipe {
|
||||
// MG_Impl/Pipe/PipeFill.cpp (the client side, the only place that may spell pGLContext).
|
||||
|
||||
@@ -75,10 +75,8 @@ namespace {
|
||||
void SetUp() override {
|
||||
m_previous = MG_State::pGLContext.Take();
|
||||
MG_State::pGLContext = MakeUnique<GLContext>();
|
||||
MGPipeSetPoisonOmission(nullptr, nullptr);
|
||||
}
|
||||
void TearDown() override {
|
||||
MGPipeSetPoisonOmission(nullptr, nullptr);
|
||||
MG_State::pGLContext = Move(m_previous);
|
||||
}
|
||||
SharedPtr<GLContext> m_previous;
|
||||
@@ -88,11 +86,20 @@ namespace {
|
||||
Bool Fresh(MGPipeInputField field) {
|
||||
return MGPipeInputFieldIsFresh(gPipeInputs->FilledState(), field);
|
||||
}
|
||||
|
||||
// Negative control B's omission, made here rather than by the library: fill `verb`, then put
|
||||
// the stamp of `field` back to what it held before the fill. The value is copied and only the
|
||||
// stamp is withheld, which is what a FillPoints.def row nobody wrote leaves behind.
|
||||
void FillWithholdingTheStamp(MGPipeVerb verb, MGPipeInputField field) {
|
||||
auto& filled = const_cast<MGPipeFilledState&>(gPipeInputs->FilledState());
|
||||
const Uint64 before = filled.FilledGen[static_cast<SizeT>(field)];
|
||||
MGPipeValidateForVerb(verb);
|
||||
filled.FilledGen[static_cast<SizeT>(field)] = before;
|
||||
}
|
||||
#endif
|
||||
|
||||
[[maybe_unused]] constexpr const char* kOmittedFatal =
|
||||
"Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@GenerateMipmap\"}";
|
||||
[[maybe_unused]] constexpr const char* kOmissionKnob = "GenerateMipmap:GetActiveTextureUnit";
|
||||
|
||||
#if MGTEST_HAVE_FORK
|
||||
struct ChildResult {
|
||||
@@ -139,7 +146,7 @@ namespace {
|
||||
// Negative control B, layer 1 (P1 brief D6 / G5): the omitted (verb, field) pair is the
|
||||
// ONLY thing that goes stale - every other bit of the verb class's mask is fresh, every
|
||||
// field outside it is not, and the verb after it neither heals the field (not in kDraw's
|
||||
// mask) nor loses one of its own.
|
||||
// mask) nor loses one of its own. The omission is the test's own (FillWithholdingTheStamp).
|
||||
TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
|
||||
#if !MOBILEGL_PIPE_POISON
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
@@ -148,8 +155,7 @@ TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
|
||||
EXPECT_TRUE(Fresh(MGPipeInputField::GetActiveTextureUnit));
|
||||
EXPECT_TRUE(Fresh(MGPipeInputField::GetTextureUnitObject));
|
||||
|
||||
MGPipeSetPoisonOmission("GenerateMipmap", "GetActiveTextureUnit");
|
||||
MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap);
|
||||
FillWithholdingTheStamp(MGPipeVerb::GenerateMipmap, MGPipeInputField::GetActiveTextureUnit);
|
||||
EXPECT_TRUE(Fresh(MGPipeInputField::GetTextureUnitObject));
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetActiveTextureUnit));
|
||||
// The value was still copied: only the stamp is withheld.
|
||||
@@ -173,15 +179,15 @@ TEST_F(PipeInputsTest, OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale) {
|
||||
// A sticky field stays fresh across every verb.
|
||||
EXPECT_TRUE(Fresh(MGPipeInputField::RecordError));
|
||||
|
||||
// And the omission is scoped to its verb: a different verb of the same class keeps it.
|
||||
// And the omission is scoped to its fill: the next verb of the same class stamps it again.
|
||||
MGPipeValidateForVerb(MGPipeVerb::BindImageTexture);
|
||||
EXPECT_TRUE(Fresh(MGPipeInputField::GetActiveTextureUnit));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Negative control B, layer 2 (G5): the read itself. The child fills GenerateMipmap with the
|
||||
// omission and reads gPipeInputs->GetActiveTextureUnit(); the parent expects SIGABRT and the
|
||||
// exact Fatal line, and that nothing else was fatal.
|
||||
// stamp of GetActiveTextureUnit withheld and reads gPipeInputs->GetActiveTextureUnit(); the
|
||||
// parent expects SIGABRT and the exact Fatal line, and that nothing else was fatal.
|
||||
TEST_F(PipeInputsTest, ReadingAnOmittedFieldAbortsNamingTheVerb) {
|
||||
#if !MOBILEGL_PIPE_POISON
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
@@ -190,10 +196,9 @@ TEST_F(PipeInputsTest, ReadingAnOmittedFieldAbortsNamingTheVerb) {
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const ChildResult r = RunInChild([] {
|
||||
MGPipeSetPoisonOmission("GenerateMipmap", "GetActiveTextureUnit");
|
||||
MGPipeValidateForVerb(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs->GetRenderStateParameters(); // a filled field of the preceding draw: must not abort
|
||||
MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap);
|
||||
FillWithholdingTheStamp(MGPipeVerb::GenerateMipmap, MGPipeInputField::GetActiveTextureUnit);
|
||||
(void)gPipeInputs->GetTextureUnitObject(0); // the sibling field: filled, must not abort
|
||||
(void)gPipeInputs->GetActiveTextureUnit(); // the omitted field: Fatal
|
||||
::_exit(3); // reached only if the poison failed
|
||||
@@ -253,53 +258,6 @@ TEST_F(PipeInputsTest, ReadingBeforeAnyFillAbortsNamingNoVerb) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// The MOBILEGL_PIPE_POISON_OMIT parser, through the Features field the loader fills: the
|
||||
// child sets the knob after its parent filled without it, and the first fill after that
|
||||
// parses it, logs the arming line and withholds the stamp exactly as the programmatic form.
|
||||
TEST_F(PipeInputsTest, PoisonOmitKnobArmsTheOmission) {
|
||||
#if !MOBILEGL_PIPE_POISON
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
#elif !MGTEST_HAVE_FORK
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
MGPipeValidateForVerb(MGPipeVerb::DrawArrays); // the parent's parse saw an empty knob
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipePoisonOmit = kOmissionKnob;
|
||||
MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap);
|
||||
(void)gPipeInputs->GetTextureUnitObject(0); // the sibling field: filled, must not abort
|
||||
(void)gPipeInputs->GetActiveTextureUnit(); // the omitted field: Fatal
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("MGPipe: poison omission armed - GetActiveTextureUnit@GenerateMipmap"), std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_NE(r.Log.find(kOmittedFatal), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("GetTextureUnitObject@"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// An unknown verb in the knob is Fatal{PipeVerifyBadKnob} naming the knob and the value,
|
||||
// before any stamp is withheld.
|
||||
TEST_F(PipeInputsTest, BadPoisonOmitKnobIsFatalNamingTheKnob) {
|
||||
#if !MGTEST_HAVE_FORK
|
||||
GTEST_SKIP() << "no fork() on this platform";
|
||||
#else
|
||||
ASSERT_FALSE(g_logPath.empty()) << "main() did not set MOBILEGL_LOG_FILE_PATH";
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Features.PipePoisonOmit = "NoSuchVerb:GetActiveTextureUnit";
|
||||
MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap);
|
||||
::_exit(3);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{PipeVerifyBadKnob, \"MOBILEGL_PIPE_POISON_OMIT=NoSuchVerb:GetActiveTextureUnit\": "
|
||||
"no such verb in kMGPipeVerbNames}"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_EQ(r.Log.find("poison omission armed"), std::string::npos) << r.Log;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Negative control A at the block level (G4): a clean snapshot compares equal to the pushed
|
||||
// block over the verb's mask; corrupting one field of the snapshot makes the compare name
|
||||
// exactly that field, at the serial of the fill it belongs to.
|
||||
@@ -566,8 +524,7 @@ TEST_F(PipeInputsTest, TheMutationNoticeRefreshesTheValueButNotTheStamp) {
|
||||
GTEST_SKIP() << "poison not compiled in (MOBILEGL_PIPE_POISON=0)";
|
||||
#else
|
||||
auto& ctx = *MG_State::pGLContext;
|
||||
MGPipeSetPoisonOmission("DrawArrays", "GetSamplingResolutionGeneration");
|
||||
MGPipeValidateForVerb(MGPipeVerb::DrawArrays);
|
||||
FillWithholdingTheStamp(MGPipeVerb::DrawArrays, MGPipeInputField::GetSamplingResolutionGeneration);
|
||||
ASSERT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration));
|
||||
ctx.BumpSamplingResolutionGeneration();
|
||||
EXPECT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration))
|
||||
@@ -575,7 +532,6 @@ TEST_F(PipeInputsTest, TheMutationNoticeRefreshesTheValueButNotTheStamp) {
|
||||
|
||||
// FenceSync is a kQuery verb: its mask holds no texture field at all, so the notice must
|
||||
// leave the generation unfilled and a read of it Fatal{UnmigratedPipeInput}.
|
||||
MGPipeSetPoisonOmission(nullptr, nullptr);
|
||||
MGPipeValidateForVerb(MGPipeVerb::FenceSync);
|
||||
ASSERT_FALSE(Fresh(MGPipeInputField::GetSamplingResolutionGeneration));
|
||||
ctx.BumpSamplingResolutionGeneration();
|
||||
|
||||
@@ -47,7 +47,7 @@ CMake:`MOBILEGL_BUILD_DISAGGREGATED`(OFF)、`MOBILEGL_BUILD_DISAGGREGATED_
|
||||
| 变量 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `MOBILEGL_PIPE_PUSH` | `0x3fff` | 子系统位图(`MG_Pipe/MGPipe.h`,位永不复用);P13 起位 0–13 固定开,清任何一位都在启动时具名拒绝;只剩位 63("关 CSO 内容寻址"的行为对照)可设 |
|
||||
| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` / `_POISON_OMIT` | 0 / 1 / 空 / 空 | 影子比对与两个阴性对照。比对器只编进 verify 构建,其他构建里 `MOBILEGL_PIPE_VERIFY=1` 只告警;`_VERIFY_CORRUPT` 不进 `Config.h`,只有 verify 构建在比对器武装时读它(`PipeFill.cpp` 的 `ArmVerify`,同样经环境与系统配置文件) |
|
||||
| `MOBILEGL_PIPE_VERIFY` / `_VERIFY_FATAL` / `_VERIFY_CORRUPT` | 0 / 1 / 空 | 影子比对与它的阴性对照。比对器只编进 verify 构建,其他构建里 `MOBILEGL_PIPE_VERIFY=1` 只告警;`_VERIFY_CORRUPT` 不进 `Config.h`,只有 verify 构建在比对器武装时读它(`PipeFill.cpp` 的 `ArmVerify`,同样经环境与系统配置文件)。poison 的阴性对照没有运行时开关:集成测试的 harness 标记 `MGITEST_POISON_OMIT` 由重新 exec 的子进程经 verify 构建才有的 `MGPipeSetPoisonOmission` 武装(`Harness/PoisonOmissionArm.h`) |
|
||||
| `MOBILEGL_PIPE_STATS` / `_STATS_PERIOD` / `_STATS_FILE` | 0 / 120 / 空 | 边界计数器(附 B);dump 按角色写 `<base>.client.json` / `<base>.server.json` |
|
||||
| `MOBILEGL_PIPE_INDEX_MIRROR_MB` | 64 | 索引镜像预算(P8);`_LEGACY_MEMOS`、`_TEXEL_RETAIN_MB`、`_HANDLE_ABA_CONTROL` 随各自的臂在 P13 删除 |
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ them wrong.
|
||||
| 2026-10-18 | `Negative control A (corrupted snapshot)` | Test / `integration-verify` | it re-ran the five `DirectGLES.Verify.ClearThenReadPixelsScenario` entries with `MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters` exported job-wide and accepted any red | the in-lane `DirectGLES/DirectVulkan.VerifyCorrupted.PipeVerifyArmingScenario.CorruptedFieldIsReported` entries, required by name (`Verify lane control entries`), which require `Fatal{PipeVerifyDiffer, "GetRenderStateParameters@...` in the log; the abort with `MOBILEGL_PIPE_VERIFY_FATAL` at its default is unit `PipeInputsTest.VerifyCorruptKnobNamesTheFieldAtEntry` on the verify runtime |
|
||||
| 2026-10-18 | `Negative control A2 (corrupted capability)` | Test / `integration-verify` | the same five-entry rerun with `MOBILEGL_PIPE_VERIFY_CORRUPT=IsCapabilityEnabled`, accepting any red; it proved that the comparator compares the capability field (P13 W3c moved the residual block's cross-check there) | unit `PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator` (`Unit tests on the verify runtime`): every field with storage, the capability field among them, corrupted in turn must be the field the comparator names |
|
||||
| 2026-10-18 | `Negative control A (corrupted snapshot)` and `Keep lane logs before controls` | Test / `integration-verify-split` | the first re-ran the ten `VerifySplit.ClearThenReadPixelsScenario` entries of both backends with the same knob and accepted any red; the second only kept the lane's logs from being overwritten by that rerun | the in-lane `VerifySplitCorrupted` and `VerifySplitReadCorrupted` entries of both backends, required by name (`Verify-split control entries`) |
|
||||
| 2026-10-18 | unit `PipeInputsTest.{PoisonOmitKnobArmsTheOmission, BadPoisonOmitKnobIsFatalNamingTheKnob}`, and the `MOBILEGL_PIPE_POISON_OMIT` knob: `Features.PipePoisonOmit`, its parser on every verb (`MGPipeValidateForVerb`'s `ParsePoisonOmissionKnob`, compiled into every build) and the server stamp's own parse of it | Test / unit (every poison lane); product code | the knob existed only for negative control B, and the two cases pinned its parser and its `Fatal{PipeVerifyBadKnob}` | the PoisonOmitted and VerifySplitPoisonOmitted entries set the harness marker `MGITEST_POISON_OMIT` to the same pair, and the re-executed child arms it through `MGPipeSetPoisonOmission`, compiled only with `-DMOBILEGL_PIPE_VERIFY=ON` (`Harness/PoisonOmissionArm.h`); `OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale`, `ReadingAnOmittedFieldAbortsNamingTheVerb` and `TheMutationNoticeRefreshesTheValueButNotTheStamp` withhold the stamp test-side and still run in every poison lane |
|
||||
|
||||
## Kept after investigation
|
||||
|
||||
|
||||
@@ -10503,14 +10503,12 @@
|
||||
"PipeCatalogue.WireOpcodesAreThePositionsInTheCatalogue",
|
||||
"PipeInputsTest.AFrontendMutationInsideAVerbDoesNotDivergeAtRead",
|
||||
"PipeInputsTest.AFrontendMutationInsideAVerbRefreshesThePushedField",
|
||||
"PipeInputsTest.BadPoisonOmitKnobIsFatalNamingTheKnob",
|
||||
"PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob",
|
||||
"PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial",
|
||||
"PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator",
|
||||
"PipeInputsTest.EveryVerbFillsItsClassAndNothingElse",
|
||||
"PipeInputsTest.MutatedFieldIsNamedAtRead",
|
||||
"PipeInputsTest.OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale",
|
||||
"PipeInputsTest.PoisonOmitKnobArmsTheOmission",
|
||||
"PipeInputsTest.ReadingAFilledFieldCompletes",
|
||||
"PipeInputsTest.ReadingAnOmittedFieldAbortsNamingTheVerb",
|
||||
"PipeInputsTest.ReadingBeforeAnyFillAbortsNamingNoVerb",
|
||||
@@ -16801,14 +16799,12 @@
|
||||
"PipeCatalogue.WireOpcodesAreThePositionsInTheCatalogue",
|
||||
"PipeInputsTest.AFrontendMutationInsideAVerbDoesNotDivergeAtRead",
|
||||
"PipeInputsTest.AFrontendMutationInsideAVerbRefreshesThePushedField",
|
||||
"PipeInputsTest.BadPoisonOmitKnobIsFatalNamingTheKnob",
|
||||
"PipeInputsTest.BadVerifyCorruptKnobIsFatalNamingTheKnob",
|
||||
"PipeInputsTest.CorruptedSnapshotFieldIsNamedWithItsSerial",
|
||||
"PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator",
|
||||
"PipeInputsTest.EveryVerbFillsItsClassAndNothingElse",
|
||||
"PipeInputsTest.MutatedFieldIsNamedAtRead",
|
||||
"PipeInputsTest.OmittingOneFieldForOneVerbLeavesExactlyThatFieldStale",
|
||||
"PipeInputsTest.PoisonOmitKnobArmsTheOmission",
|
||||
"PipeInputsTest.ReadingAFilledFieldCompletes",
|
||||
"PipeInputsTest.ReadingAnOmittedFieldAbortsNamingTheVerb",
|
||||
"PipeInputsTest.ReadingBeforeAnyFillAbortsNamingNoVerb",
|
||||
|
||||
@@ -237,8 +237,8 @@ endif()
|
||||
# * it carries none of the three MGPipe Fatals: Fatal{PipeVerifyDiffer (a push/pull divergence,
|
||||
# the thing the mode exists to find), Fatal{UnmigratedPipeInput (a backend read of a field the
|
||||
# verb's fill table does not list - fixed by adding the row to MG_Pipe/FillPoints.def, never by
|
||||
# marking the field sticky), and Fatal{PipeVerifyBadKnob (a misspelt MOBILEGL_PIPE_VERIFY_CORRUPT
|
||||
# or MOBILEGL_PIPE_POISON_OMIT). The third is in the regex on purpose even though D2 makes it
|
||||
# marking the field sticky), and Fatal{PipeVerifyBadKnob (a misspelt MOBILEGL_PIPE_VERIFY_CORRUPT).
|
||||
# The third is in the regex on purpose even though D2 makes it
|
||||
# abort the process: with MOBILEGL_PIPE_VERIFY_FATAL=0 the abort is exactly what does not
|
||||
# happen, and a typo'd knob would otherwise leave the negative-control lane looking healthy.
|
||||
# The Fatal check is not redundant with the replay's exit status: MOBILEGL_PIPE_VERIFY_FATAL=0 is
|
||||
@@ -286,8 +286,8 @@ if(DEFINED ENV{MOBILEGL_PIPE_VERIFY} AND NOT "$ENV{MOBILEGL_PIPE_VERIFY}" STREQU
|
||||
"divergence and is recorded, not silenced; Fatal{UnmigratedPipeInput, \"<Field>@<Verb>\"} "
|
||||
"is a missing row in MG_Pipe/FillPoints.def's class table - add it, regenerate, rerun "
|
||||
"(never mark the field sticky); Fatal{PipeVerifyBadKnob, ...} is a misspelt "
|
||||
"MOBILEGL_PIPE_VERIFY_CORRUPT / MOBILEGL_PIPE_POISON_OMIT - fix the spelling, the "
|
||||
"vocabularies are kMGPipeInputFieldNames[] and kMGPipeVerbNames[].")
|
||||
"MOBILEGL_PIPE_VERIFY_CORRUPT - fix the spelling, the vocabulary is "
|
||||
"kMGPipeInputFieldNames[].")
|
||||
endif()
|
||||
message(STATUS "MGPipe verify: ${pipe_verify_case} armed, zero divergences, zero unmigrated reads")
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user