mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Pipe, MG_Test, CI] (Disaggregated): P5f close - retire residual pointer accessors and enforce a zero-debt table
This commit is contained in:
@@ -435,7 +435,11 @@ namespace MobileGL::MG_Pipe {
|
||||
// PipeInputs.h's F-class block), so freshness can never reach them and neither can the
|
||||
// stamp's withdrawal. This is the only thing that puts them in `rsp`.
|
||||
if (!gPipeInputs.ServerStampedVerb()) return;
|
||||
CountBarrierPull(field, gPipeInputs.CurrentVerb());
|
||||
const auto ownership = MGPipeFieldOwnershipOf(field);
|
||||
if (ownership == MGPipeFieldOwnership::kFatal)
|
||||
MGPipeInputPoisonFatalForVerb(field, gPipeInputs.CurrentVerb());
|
||||
if (ownership == MGPipeFieldOwnership::kBarrierPulled)
|
||||
CountBarrierPull(field, gPipeInputs.CurrentVerb());
|
||||
}
|
||||
#endif // MOBILEGL_BUILD_DISAGGREGATED
|
||||
|
||||
|
||||
@@ -2555,50 +2555,12 @@ namespace MobileGL::MG_Pipe {
|
||||
kMGPipeWiredBufferBindingSubsystem == kMGPipeSubsystemBufferBindings,
|
||||
"ShaderBufferEmit.h's wired constant must be 0 or the binding-point bit");
|
||||
|
||||
// A field an emitted call supplies COMPLETELY, so the residual fill may stop pulling
|
||||
// it. Two rows of Coverage.def's emitted list do not qualify and each has its reason
|
||||
// recorded here rather than a silent absence:
|
||||
//
|
||||
// GetPixelStoreParameters is BOTH halves of the pixel store (m_pixelStore[0] pack
|
||||
// and [1] unpack) and set_pixel_pack_state deliberately carries only PACK
|
||||
// (ARCHITECTURE.md 4.6 D5, MGPipeTypes.h). The unpack half has no carrier at all,
|
||||
// so the field keeps being pulled and the verify comparator keeps proving it.
|
||||
//
|
||||
// GetBoundVertexArray is P3a's row, and Coverage.def asks for the decision to be
|
||||
// taken HERE, deliberately, rather than inherited from the row's presence. THE
|
||||
// ANSWER IS NO, and it is not a matter of degree: the field's storage is a
|
||||
// SharedPtr<VertexArrayObject> - a frontend heap reference - and the call that
|
||||
// supplies it, bind_vertex_elements, carries an eight-byte {slot, gen} handle
|
||||
// and nothing else. The applier stores that handle in
|
||||
// MGPipeApplierState::BoundVertexElements; it has no way to produce the pointer,
|
||||
// and P3a deliberately does not give it one (a payload never contains a pointer,
|
||||
// and the whole point of the conversion is that the server stops holding
|
||||
// frontend references). Skipping the pull would leave m_boundVertexArray null on
|
||||
// every draw of every push build - which is not a subtle staleness, it is every
|
||||
// backend read of the bound VAO reading nothing.
|
||||
//
|
||||
// So the row is EMITTED-AND-STILL-PULLED, exactly like GetPixelStoreParameters:
|
||||
// the call goes out because the server needs the format, and the field keeps
|
||||
// coming through the residual fill because the mirror is a pointer only the
|
||||
// client can hold. What retires the pull is not a better applier - it is P8,
|
||||
// where the backend stops reading a frontend VAO at all.
|
||||
// P4a's SIX ROWS ARE ALL FALSE, and five of them for GetBoundVertexArray's exact
|
||||
// reason: the field's storage is a frontend heap reference - a
|
||||
// BindingSlot<FramebufferObject>, an ImageTextureBinding, a TextureUnit, two
|
||||
// SharedPtr<ProgramObject> - and the calls that supply them carry eight-byte
|
||||
// {slot, gen} handles and fully resolved descriptors. The applier has no way to
|
||||
// produce a pointer and P4a deliberately does not give it one: a payload never
|
||||
// contains a pointer, and the whole point of the conversion is that the server
|
||||
// stops holding frontend references. Skipping the pull would leave those mirrors
|
||||
// null on every draw of every push build. What retires them is not a better
|
||||
// applier, it is the phase where the backend stops reading a frontend object.
|
||||
//
|
||||
// GetMaxTouchedTextureUnit was the sixth and its argument was different - a plain
|
||||
// Int whose carrier (set_sampler_views' Count) is hash-suppressed while the
|
||||
// high-water mark still moves on a redundant re-bind. P5c rv RETIRED it from this
|
||||
// list (CONTRACT-P5C.md §5.3): set_context_values carries the mark as a VALUE of
|
||||
// its own, whole-record suppressed, so the lag the suppressor could introduce is
|
||||
// gone and the derivation's RECORD_SUPPLIED answer is honest.
|
||||
// Does the record supply this exact getter's representation? P5f consumers now
|
||||
// read handle/range records directly, while the old pointer getters are FATAL on
|
||||
// the server. A handle is still not a SharedPtr/client table base, so these false
|
||||
// arms remain: otherwise the ownership generator would misclassify the retired
|
||||
// getters as RECORD_SUPPLIED. Monolith still fills its original pointer mirrors.
|
||||
// PixelStoreParameters has two halves and only PACK is supplied.
|
||||
constexpr Bool EmittedCallSuppliesTheWholeField(MGPipeInputField field) {
|
||||
switch (field) {
|
||||
case MGPipeInputField::GetPixelStoreParameters:
|
||||
@@ -2608,12 +2570,8 @@ namespace MobileGL::MG_Pipe {
|
||||
case MGPipeInputField::GetTextureUnitObject:
|
||||
case MGPipeInputField::GetProgramForDraw:
|
||||
case MGPipeInputField::GetProgramForDispatch:
|
||||
// P5e (CONTRACT-P5E.md §5.6): the same answer for the same reason. The field is
|
||||
// four raw bases into the frontend's binding-point table and set_shader_buffers
|
||||
// carries resolved {handle, offset, size} ranges; the applier cannot produce a
|
||||
// pointer, so skipping the pull would leave the mirror null on every draw of every
|
||||
// push build. What retires it is the four Espryt consumers reading the applier's
|
||||
// BoundShaderBuffers, not this row.
|
||||
// Indexed binding points also have a different representation: ranges on
|
||||
// the server, raw client table bases in the legacy getter.
|
||||
case MGPipeInputField::GetBufferBindingPoint:
|
||||
return false;
|
||||
default:
|
||||
|
||||
@@ -6,201 +6,57 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
// The hand-maintained half of TABLE 2 (CONTRACT-P5.md section 3, R-7): where every
|
||||
// PipeInputs field's value comes from once the backend is a server.
|
||||
// P5f exit classification. This table describes SERVER reads, not monolith storage.
|
||||
// RECORD_SUPPLIED is still derived from Coverage.def minus the whole-field refusal set.
|
||||
// A handle record does not supply a legacy pointer-valued getter: those getters are FATAL,
|
||||
// while their migrated consumers read the server record directly. Never relabel a pointer
|
||||
// RECORD_SUPPLIED merely to make the debt count zero.
|
||||
//
|
||||
// scripts/gen_pipe_field_ownership.py joins this file against the DERIVED half and writes
|
||||
// generated/PipeFieldOwnership.inc. CI runs `--check` and `--self-test` beside gen_pipe.py's.
|
||||
// APPLIER_DERIVED accessors answer server state. FATAL accessors must abort on any server
|
||||
// read, including barriered/escalated records and non-strict/shared-block configurations.
|
||||
// BARRIER_PULLED remains a schema class so generator controls can test debt admission,
|
||||
// but no production row may use it after P5f. Monolith getter/fill behavior is unchanged.
|
||||
//
|
||||
// THE FOUR CLASSES (CONTRACT-P5.md section 3):
|
||||
//
|
||||
// RECORD_SUPPLIED a pushed record supplies the WHOLE field, so the server never needs
|
||||
// the client for it. THIS CLASS IS DERIVED, NOT LISTED: it is
|
||||
// kMGPipeFieldEmittedBy != kNone (Coverage.def's MGP_COVERAGE_EMITTED_LIST)
|
||||
// minus the fields EmittedCallSuppliesTheWholeField refuses
|
||||
// (MG_Impl/Pipe/PipeFill.cpp). 41 fields since P5c rv (32 before it). A row
|
||||
// below that names a
|
||||
// field the derivation already placed here is a CONTRADICTION and stops
|
||||
// the generator - which is the only way this file can stay true as the
|
||||
// emitted list grows.
|
||||
//
|
||||
// APPLIER_DERIVED the applier writes it out of records it already applies, but no row of
|
||||
// the emitted list claims it, so the derivation above cannot see it.
|
||||
//
|
||||
// BARRIER_PULLED P5's DEBT. The server answers by reading a value the client's residual
|
||||
// fill (PipeFill.cpp step 4) left in the single shared gPipeInputs while
|
||||
// the verb barrier holds both threads apart (R-1). Correct only because
|
||||
// of that barrier, which is why the barrier is load-bearing rather than
|
||||
// cautious. Every row NAMES THE PHASE THAT RETIRES IT; a row with no
|
||||
// phase stops the generator. Each such read increments
|
||||
// PipeStats::CallClass::ResidualPulls (`rsp`) and, under
|
||||
// MOBILEGL_IPC_STRICT_ERRORS=1, is Fatal.
|
||||
//
|
||||
// FATAL no carrier, and the reduced path never reads it, so a read is a real
|
||||
// defect: Fatal{UnmigratedPipeInput, "<Field>@<verb>"}.
|
||||
//
|
||||
// A field in NONE of the four is a generator error and therefore a build failure (R-7.1).
|
||||
// A field in TWO is the same. That is the whole mechanism: a hand-maintained table would be
|
||||
// wrong within a week, and this one cannot be silently incomplete.
|
||||
// EmittedCallSuppliesTheWholeField retains its eight refusals: seven legacy pointer
|
||||
// representations plus the two-sided pixel-store field. Deleting those refusals would
|
||||
// falsely derive RECORD_SUPPLIED for the retired getters; server consumers bypass them.
|
||||
//
|
||||
// clang-format off
|
||||
|
||||
// X(Field, Class, RetiringPhase, Why)
|
||||
//
|
||||
// RetiringPhase is `-` for every class but BARRIER_PULLED, where it is the ROADMAP phase
|
||||
// whose row stops the pull. It is a string, not an enum, because two of them name two
|
||||
// phases for the two backends and flattening that would lose the half that matters.
|
||||
//
|
||||
// P5f fm: the Magma transport consumers of the nine object rows below have retired.
|
||||
// WireDraw/WireFramebuffer and MagmaProgramSource/UniformManager read server records;
|
||||
// enabled vertex arrays, EBO/indirect/named UBO/SSBO/texel-buffer uses meet the existing
|
||||
// named P7 buffer refusal before any frontend read. Texture sync uses handle-keyed images
|
||||
// and staged bytes, never GetTextureObject. The old getters remain reachable only through
|
||||
// the monolith implementation (or behind the named P7/class-C boundary). Keep these global
|
||||
// classifications until fe + fm are integrated and the residual-fill/generator invariants
|
||||
// are retired together; a missing wire branch must still poison, never read a default.
|
||||
// RecordError belongs to fv. Details and the no-buffer draw counterexample are in
|
||||
// docs/Disaggregated/notes/p5f/fm-report.md.
|
||||
#define MGP_FIELD_OWNERSHIP_LIST(X) \
|
||||
/* ---- BARRIER_PULLED: the 9 non-sticky rows the reduced path actually reads ---- */ \
|
||||
/* scout-unmigrated-census section 3 intersects each verb class's may-read mask with "no */ \
|
||||
/* record supplies it" and unions kClear (7 of 18), kDraw (19 of 47) and kReadback (12 of */ \
|
||||
/* 17). That union was 21 fields at P5; P5c rv (CONTRACT-P5C.md §5.3) retired NINE of them */ \
|
||||
/* to RECORD_SUPPLIED through set_context_values (the two texture-unit counters, the */ \
|
||||
/* touched-count array, the five XFB values) plus GetCurrentVertexAttribute (the amended */ \
|
||||
/* set_vertex_attrib_defaults payload), and moved the three texture SHUTTERS to */ \
|
||||
/* APPLIER_DERIVED (their own rows said it since P5: "a shutter, not a value: the server */ \
|
||||
/* answers from its own Serial" - rv made the accessor DO it). GetPixelStoreParameters is */ \
|
||||
/* the 21st of the census and it is APPLIER_DERIVED below, for the reason written there. */ \
|
||||
/* WHAT IS LEFT IS EXACTLY THE OBJECT CLASS: nine fields whose storage is a frontend heap */ \
|
||||
/* reference no record can carry, each with the phase that retires it, and */ \
|
||||
/* FieldOwnershipTest pins this list as ALL the remaining non-sticky rows. OpenRA adds no */ \
|
||||
/* field to this set - it widens the SITE set, not the field set. */ \
|
||||
/* DirectGLES.cpp:4486, PrepareForDraw, UNCONDITIONAL ON EVERY DRAW. No #if, no arm guard, */ \
|
||||
/* no record fallback, and PipeFill.cpp:1902-1905 says outright that what retires the pull */ \
|
||||
/* is P8, not a better applier: the storage is a SharedPtr<VertexArrayObject> and */ \
|
||||
/* bind_vertex_elements carries an eight-byte {slot, gen}. */ \
|
||||
X(GetBoundVertexArray, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend heap reference; the record carries a handle, the mirror is a pointer") \
|
||||
/* 18 Espryt sites + 11 Magma. Coverage.def:37-70 splits the 15 BufferTargets across three */ \
|
||||
/* calls and leaves SEVEN with no carrier at all (CopyRead, CopyWrite, PixelPack, */ \
|
||||
/* PixelUnpack, Texture, DispatchIndirect, Query), which is why the FIELD is pulled even */ \
|
||||
/* though eight targets are covered. */ \
|
||||
X(GetBufferBindingSlot, BARRIER_PULLED, "P8 (indirect), P9 (readback), P13 (transfer)", \
|
||||
"7 of 15 BufferTargets have no call; the field is one array over all 15") \
|
||||
X(GetBufferBindingPoint, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend BindingSlotRange1D pointer") \
|
||||
/* 8 Espryt sites through GetFramebufferBindingSlotChecked + 13 Magma. SyncCurrentFBO */ \
|
||||
/* (:2995) is SELF-DECLARED monolith glue (DirectGLES.cpp:2961-2965) while BindCurrentFBO */ \
|
||||
/* (:4303-4353) is already split-clean - the bind is migrated, the sync is not. */ \
|
||||
X(GetFramebufferBindingSlot, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"P5f fe: Espryt reads framebuffer records; Magma retains frontend pointer debt") \
|
||||
X(GetImageTextureBinding, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend ImageTextureBinding base pointer") \
|
||||
X(GetTextureUnitObject, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"P5f fe: Espryt transport readers retired; Magma retains texture-unit debt") \
|
||||
/* DirectGLES.cpp:4497, PrepareForDraw, the second unconditional pointer read of every draw. */ \
|
||||
X(GetProgramForDraw, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||
/* P5b package i1 (CONTRACT-P5B.md §6.9, the one row the contract grants a package). This */ \
|
||||
/* was FATAL "reachable only from kDispatch; there is no compute on the reduced path" - and */ \
|
||||
/* i1 IS what puts compute on the path: launch_grid (60) now crosses and the backend's */ \
|
||||
/* PrepareForCompute pulls this inside it (DirectGLES.cpp:5779, VulkanRenderer.cpp:7327). */ \
|
||||
/* It is GetProgramForDraw's twin in every respect, so it takes its class and its retiring */ \
|
||||
/* phases. Overturned by: nothing in P5b; P7/P8 retire both rows together. */ \
|
||||
X(GetProgramForDispatch, BARRIER_PULLED, "P5e (Espryt unbarriered), P7 (Magma)", \
|
||||
"frontend SharedPtr<ProgramObject>; the record carries a handle") \
|
||||
/* The XFB family's one OBJECT row. XFB itself is off the reduced path, but kDraw's */ \
|
||||
/* may-read mask carries it and the draw walk reads it regardless - which is exactly the */ \
|
||||
/* case a field census taken from "what the scenario does" rather than from the mask would */ \
|
||||
/* miss. The family's five VALUE rows rode set_context_values out of this list at P5c rv. */ \
|
||||
X(GetTransformFeedbackProgram, BARRIER_PULLED, "P3b/P4b (Espryt), P7 (Magma)", \
|
||||
"P5f fe: Espryt capture uses the Begin snapshot; Magma retains program debt") \
|
||||
\
|
||||
/* ---- APPLIER_DERIVED ---- */ \
|
||||
/* THE FIRST ROW, and it is the one the contract asked P5 to split into two field ids. It is */ \
|
||||
/* split HERE INSTEAD, by ARGUMENT, in the argument-exception list below - see that list's */ \
|
||||
/* header for the evidence and for what would overturn the decision. The field itself is */ \
|
||||
/* APPLIER_DERIVED because set_pixel_pack_state arrives and MGPipeApplyAccess::PackState */ \
|
||||
/* writes m_pixelStore[0] out of it (PipeApply.cpp:1373), while no row of the emitted list */ \
|
||||
/* claims the field (Coverage.def:173 keeps it out deliberately, because the record supplies */ \
|
||||
/* half of it). */ \
|
||||
X(GetPixelStoreParameters, APPLIER_DERIVED, "-", \
|
||||
"set_pixel_pack_state; the applier writes m_pixelStore[0] (PipeApply.cpp:1373)") \
|
||||
/* THE THREE TEXTURE SHUTTERS (P5c rv, CONTRACT-P5C.md §5.3). No call carries them and none */ \
|
||||
/* should (Coverage.def's emitted-list header): what replaces them server-side is the */ \
|
||||
/* applier's own Serial - a different value with a different owner. rv is the edit that */ \
|
||||
/* makes the accessor DO it: under a server-stamped verb the PipeInputs accessor answers */ \
|
||||
/* MGPipeApplierTextureShutterSerial() / MGPipeApplierContextSerial() (PipeApply.cpp) */ \
|
||||
/* instead of the client's residual-fill copy, and the class flips from BARRIER_PULLED to */ \
|
||||
/* here. Under monolith the storage answer is kept, byte for byte (G1). */ \
|
||||
X(GetSamplingResolutionGeneration, APPLIER_DERIVED, "-", \
|
||||
"a shutter, not a value: the server answers from its own Serial") \
|
||||
X(GetTextureBindGeneration, APPLIER_DERIVED, "-", \
|
||||
"a shutter, not a value: the server answers from its own Serial") \
|
||||
X(GetTextureContextId, APPLIER_DERIVED, "-", \
|
||||
"a shutter, not a value: the server answers from its own Serial") \
|
||||
\
|
||||
/* ---- FATAL: two non-sticky fields, each off the reduced path for a checkable reason ----- */ \
|
||||
/* Three until P5b: GetProgramForDispatch moved up to BARRIER_PULLED when package i1 put */ \
|
||||
/* compute on the path (CONTRACT-P5B.md §6.9). */ \
|
||||
X(GetBoundTransformFeedbackName, FATAL, "-", \
|
||||
"DEAD: read by no backend since the D21 rekey (PipeInputs.h:232-234)") \
|
||||
X(GetTransformFeedbackPausedPrimitiveCounter, FATAL, "-", \
|
||||
"reachable only from class kQuery, which the reduced path never enters") \
|
||||
\
|
||||
/* ---- the seven sticky forwards, as FIELD rows ---- */ \
|
||||
/* They have no storage, so a read of the FIELD is a call of the FORWARD; the field row and */ \
|
||||
/* the forward row below carry the same class by construction, and the generator refuses a */ \
|
||||
/* pair that disagrees. Their poison exemption (generated/PipeFilled.inc:422's sticky arm) */ \
|
||||
/* is cancelled under split by the server stamp, which zeroes their FilledGen - and 0 loses */ \
|
||||
/* to nothing, because :419's "never filled" test runs first. */ \
|
||||
X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", \
|
||||
"P5f fe: server constant binding-point capacity, no frontend lookup") \
|
||||
X(GetProgramObject, BARRIER_PULLED, "P9", \
|
||||
"hands the backend a frontend SharedPtr<ProgramObject> keyed by GL name") \
|
||||
X(GetTextureObject, BARRIER_PULLED, "P7", \
|
||||
"hands the backend a frontend SharedPtr<ITextureObject> keyed by GL name") \
|
||||
X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", \
|
||||
"P5f fe: begin/end_stream_output maintain server spans by lifetime id") \
|
||||
X(ValidateProgramName, BARRIER_PULLED, "P9", \
|
||||
"a frontend name-table probe; Coverage.def calls it kClientResolved") \
|
||||
/* P5c ev DELETED the forward under an active transport (CONTRACT-P5C section 4.2): */ \
|
||||
/* R-12's caps re-publication (CapsMirror.cpp:78-80) already invalidates the compile */ \
|
||||
/* environment on the client, so a read or call under split is a real defect and */ \
|
||||
/* aborts, never a pull. The class is unconditional because FieldOwnership only ever */ \
|
||||
/* describes the split server's reads - under monolith there is no server to read. */ \
|
||||
X(InvalidateCompileEnv, FATAL, "-", \
|
||||
"DELETED under split by P5c ev; R-12's caps re-publication replaced it") \
|
||||
X(RecordError, BARRIER_PULLED, "P9", \
|
||||
"a WRITE INTO THE FRONTEND; R-12's OnGlError, whose ordering is P9's")
|
||||
#define MGP_FIELD_OWNERSHIP_LIST(X) \
|
||||
X(GetBoundVertexArray, FATAL, "-", "server reads vertex-element handles; the legacy SharedPtr is not a wire value") \
|
||||
X(GetBufferBindingSlot, FATAL, "-", "server reads verb buffer handles/reply scratch; legacy binding-slot pointers are forbidden") \
|
||||
X(GetBufferBindingPoint, FATAL, "-", "server reads indexed buffer ranges or XFB Begin targets; never a client table base") \
|
||||
X(GetFramebufferBindingSlot, FATAL, "-", "server reads framebuffer records; never a client BindingSlot") \
|
||||
X(GetImageTextureBinding, FATAL, "-", "server reads shader-image records; never the client image-unit array") \
|
||||
X(GetTextureUnitObject, FATAL, "-", "server reads sampler/view records or verb texture handles; never a client TextureUnit") \
|
||||
X(GetProgramForDraw, FATAL, "-", "server reads draw-program handles and its owned ProgramArchive") \
|
||||
X(GetProgramForDispatch, FATAL, "-", "server reads dispatch-program handles and its owned ProgramArchive") \
|
||||
X(GetTransformFeedbackProgram, FATAL, "-", "server reads the capture-program archive retained by the Begin snapshot") \
|
||||
X(GetBoundTransformFeedbackName, FATAL, "-", "no backend reader; XFB identity crosses as a lifetime value") \
|
||||
X(GetTransformFeedbackPausedPrimitiveCounter, FATAL, "-", "query-only legacy accessor; transport entry is a named class-C refusal") \
|
||||
X(GetProgramObject, FATAL, "-", "program identity is resolved on the client and crosses as a shader-CSO handle") \
|
||||
X(GetTextureObject, FATAL, "-", "texture identity crosses as a handle; no server GL-name lookup into the client") \
|
||||
X(ValidateProgramName, FATAL, "-", "validation belongs to the client; the server validates the carried handle") \
|
||||
X(InvalidateCompileEnv, FATAL, "-", "transport caps republication replaces direct frontend invalidation") \
|
||||
X(RecordError, FATAL, "-", "transport OnGlError posts an event before the legacy frontend-error-queue arm") \
|
||||
X(GetPixelStoreParameters, APPLIER_DERIVED, "-", "set_pixel_pack_state writes the pack half; unpack is forbidden below") \
|
||||
X(GetSamplingResolutionGeneration, APPLIER_DERIVED, "-", "server-owned applier texture shutter serial") \
|
||||
X(GetTextureBindGeneration, APPLIER_DERIVED, "-", "server-owned applier texture shutter serial") \
|
||||
X(GetTextureContextId, APPLIER_DERIVED, "-", "server-owned applier context serial") \
|
||||
X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", "fixed indexed binding-point capacity; no client lookup") \
|
||||
X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", "server Begin/End snapshot table keyed by XFB lifetime id")
|
||||
|
||||
// X(Field, Class, RetiringPhase, Mechanism) - the SEVEN STICKY FORWARDS as their own rows.
|
||||
//
|
||||
// CONTRACT-P5.md section 3: "The domain is 63 fields plus the 7 sticky forwards, which are
|
||||
// among those 63 but are exempted from the poison and so need their own row. 70 rows."
|
||||
// The reason they need a second row is that they are the seven that hand the server a raw
|
||||
// FRONTEND OBJECT or write INTO the frontend, so "the exit gate is structurally blind on the
|
||||
// seven most dangerous fields" - and the row that fixes that is about the FORWARD (a live
|
||||
// call with no stored value), not about the field's storage, which does not exist.
|
||||
//
|
||||
// Mechanism is what replaces the forward, which is NOT the same question as which phase
|
||||
// retires the pull.
|
||||
#define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X) \
|
||||
X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", \
|
||||
"a server-side binding-point table") \
|
||||
X(GetProgramObject, BARRIER_PULLED, "P9", \
|
||||
"a client-resolved program handle table (ARCHITECTURE 3.2 explicitly-not-ported)") \
|
||||
X(GetTextureObject, BARRIER_PULLED, "P7", \
|
||||
"a server-side texture handle table") \
|
||||
X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", \
|
||||
"server-side XFB span state") \
|
||||
X(ValidateProgramName, BARRIER_PULLED, "P9", \
|
||||
"a client-resolved program name probe") \
|
||||
X(InvalidateCompileEnv, FATAL, "-", \
|
||||
"DELETED under split by P5c ev; the re-arriving caps snapshot IS the invalidation (R-12)") \
|
||||
X(RecordError, BARRIER_PULLED, "P9", \
|
||||
"OnGlError, the ordered reverse-channel error post (R-12); its ordering is P9's")
|
||||
// The seven sticky forwards carry the same verdict as their field rows.
|
||||
#define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X) \
|
||||
X(GetBufferBindingPointCount, APPLIER_DERIVED, "-", "fixed indexed binding-point capacity; no client lookup") \
|
||||
X(GetProgramObject, FATAL, "-", "program identity is resolved on the client and crosses as a shader-CSO handle") \
|
||||
X(GetTextureObject, FATAL, "-", "texture identity crosses as a handle; no server GL-name lookup into the client") \
|
||||
X(HasOpenTransformFeedbackSpan, APPLIER_DERIVED, "-", "server Begin/End snapshot table keyed by XFB lifetime id") \
|
||||
X(ValidateProgramName, FATAL, "-", "validation belongs to the client; the server validates the carried handle") \
|
||||
X(InvalidateCompileEnv, FATAL, "-", "transport caps republication replaces direct frontend invalidation") \
|
||||
X(RecordError, FATAL, "-", "transport OnGlError posts an event before the legacy frontend-error-queue arm")
|
||||
|
||||
// X(Field, Arg0, Class, Why) - ARGUMENT-KEYED EXCEPTIONS.
|
||||
//
|
||||
|
||||
@@ -39,9 +39,9 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetBlendEquationIndexed
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetBlendFuncIndexed
|
||||
MGPipeFieldOwnership::kFatal, // GetBoundTransformFeedbackName
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetBoundVertexArray
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingSlot
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetBufferBindingPoint
|
||||
MGPipeFieldOwnership::kFatal, // GetBoundVertexArray
|
||||
MGPipeFieldOwnership::kFatal, // GetBufferBindingSlot
|
||||
MGPipeFieldOwnership::kFatal, // GetBufferBindingPoint
|
||||
MGPipeFieldOwnership::kApplierDerived, // GetBufferBindingPointCount
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetTouchedBufferBindingPointCount
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetClampReadColor
|
||||
@@ -54,8 +54,8 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetDepthFunc
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetDepthMask
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetDepthRangeIndexed
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetFramebufferBindingSlot
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetImageTextureBinding
|
||||
MGPipeFieldOwnership::kFatal, // GetFramebufferBindingSlot
|
||||
MGPipeFieldOwnership::kFatal, // GetImageTextureBinding
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetLineWidth
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetLogicOp
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetMaxTouchedTextureUnit
|
||||
@@ -69,9 +69,9 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetFactor
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetPolygonOffsetUnits
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetPrimitiveRestartIndex
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramForDispatch
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramForDraw
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramObject
|
||||
MGPipeFieldOwnership::kFatal, // GetProgramForDispatch
|
||||
MGPipeFieldOwnership::kFatal, // GetProgramForDraw
|
||||
MGPipeFieldOwnership::kFatal, // GetProgramObject
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetProvokingVertexMode
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetRenderStateParameters
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetRenderStateParametersVersion
|
||||
@@ -80,12 +80,12 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetStencilState
|
||||
MGPipeFieldOwnership::kApplierDerived, // GetTextureBindGeneration
|
||||
MGPipeFieldOwnership::kApplierDerived, // GetTextureContextId
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetTextureObject
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetTextureUnitObject
|
||||
MGPipeFieldOwnership::kFatal, // GetTextureObject
|
||||
MGPipeFieldOwnership::kFatal, // GetTextureUnitObject
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetTransformFeedbackCapturedVertices
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetTransformFeedbackGeneration
|
||||
MGPipeFieldOwnership::kFatal, // GetTransformFeedbackPausedPrimitiveCounter
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetTransformFeedbackProgram
|
||||
MGPipeFieldOwnership::kFatal, // GetTransformFeedbackProgram
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetViewport
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetViewportIndexed
|
||||
MGPipeFieldOwnership::kRecordSupplied, // IsCapabilityEnabled
|
||||
@@ -93,8 +93,8 @@ inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnership[kMGPipeInputFieldCou
|
||||
MGPipeFieldOwnership::kRecordSupplied, // IsTransformFeedbackActive
|
||||
MGPipeFieldOwnership::kRecordSupplied, // IsTransformFeedbackPaused
|
||||
MGPipeFieldOwnership::kFatal, // InvalidateCompileEnv
|
||||
MGPipeFieldOwnership::kBarrierPulled, // ValidateProgramName
|
||||
MGPipeFieldOwnership::kBarrierPulled, // RecordError
|
||||
MGPipeFieldOwnership::kFatal, // ValidateProgramName
|
||||
MGPipeFieldOwnership::kFatal, // RecordError
|
||||
MGPipeFieldOwnership::kRecordSupplied, // GetBoundTransformFeedbackLifetimeId
|
||||
MGPipeFieldOwnership::kApplierDerived, // HasOpenTransformFeedbackSpan
|
||||
};
|
||||
@@ -106,9 +106,9 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetBlendEquationIndexed
|
||||
"-", // GetBlendFuncIndexed
|
||||
"-", // GetBoundTransformFeedbackName
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetBoundVertexArray
|
||||
"P8 (indirect), P9 (readback), P13 (transfer)", // GetBufferBindingSlot
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetBufferBindingPoint
|
||||
"-", // GetBoundVertexArray
|
||||
"-", // GetBufferBindingSlot
|
||||
"-", // GetBufferBindingPoint
|
||||
"-", // GetBufferBindingPointCount
|
||||
"-", // GetTouchedBufferBindingPointCount
|
||||
"-", // GetClampReadColor
|
||||
@@ -121,8 +121,8 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetDepthFunc
|
||||
"-", // GetDepthMask
|
||||
"-", // GetDepthRangeIndexed
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetFramebufferBindingSlot
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetImageTextureBinding
|
||||
"-", // GetFramebufferBindingSlot
|
||||
"-", // GetImageTextureBinding
|
||||
"-", // GetLineWidth
|
||||
"-", // GetLogicOp
|
||||
"-", // GetMaxTouchedTextureUnit
|
||||
@@ -136,9 +136,9 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetPolygonOffsetFactor
|
||||
"-", // GetPolygonOffsetUnits
|
||||
"-", // GetPrimitiveRestartIndex
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetProgramForDispatch
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetProgramForDraw
|
||||
"P9", // GetProgramObject
|
||||
"-", // GetProgramForDispatch
|
||||
"-", // GetProgramForDraw
|
||||
"-", // GetProgramObject
|
||||
"-", // GetProvokingVertexMode
|
||||
"-", // GetRenderStateParameters
|
||||
"-", // GetRenderStateParametersVersion
|
||||
@@ -147,12 +147,12 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // GetStencilState
|
||||
"-", // GetTextureBindGeneration
|
||||
"-", // GetTextureContextId
|
||||
"P7", // GetTextureObject
|
||||
"P5e (Espryt unbarriered), P7 (Magma)", // GetTextureUnitObject
|
||||
"-", // GetTextureObject
|
||||
"-", // GetTextureUnitObject
|
||||
"-", // GetTransformFeedbackCapturedVertices
|
||||
"-", // GetTransformFeedbackGeneration
|
||||
"-", // GetTransformFeedbackPausedPrimitiveCounter
|
||||
"P3b/P4b (Espryt), P7 (Magma)", // GetTransformFeedbackProgram
|
||||
"-", // GetTransformFeedbackProgram
|
||||
"-", // GetViewport
|
||||
"-", // GetViewportIndexed
|
||||
"-", // IsCapabilityEnabled
|
||||
@@ -160,8 +160,8 @@ inline constexpr const char* kMGPipeFieldRetiringPhase[kMGPipeInputFieldCount] =
|
||||
"-", // IsTransformFeedbackActive
|
||||
"-", // IsTransformFeedbackPaused
|
||||
"-", // InvalidateCompileEnv
|
||||
"P9", // ValidateProgramName
|
||||
"P9", // RecordError
|
||||
"-", // ValidateProgramName
|
||||
"-", // RecordError
|
||||
"-", // GetBoundTransformFeedbackLifetimeId
|
||||
"-", // HasOpenTransformFeedbackSpan
|
||||
};
|
||||
@@ -183,21 +183,21 @@ inline constexpr MGPipeInputField kMGPipeFieldOwnershipForwardField[kMGPipeField
|
||||
};
|
||||
inline constexpr MGPipeFieldOwnership kMGPipeFieldOwnershipForward[kMGPipeFieldOwnershipForwardCount] = {
|
||||
MGPipeFieldOwnership::kApplierDerived, // GetBufferBindingPointCount
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetProgramObject
|
||||
MGPipeFieldOwnership::kBarrierPulled, // GetTextureObject
|
||||
MGPipeFieldOwnership::kFatal, // GetProgramObject
|
||||
MGPipeFieldOwnership::kFatal, // GetTextureObject
|
||||
MGPipeFieldOwnership::kApplierDerived, // HasOpenTransformFeedbackSpan
|
||||
MGPipeFieldOwnership::kBarrierPulled, // ValidateProgramName
|
||||
MGPipeFieldOwnership::kFatal, // ValidateProgramName
|
||||
MGPipeFieldOwnership::kFatal, // InvalidateCompileEnv
|
||||
MGPipeFieldOwnership::kBarrierPulled, // RecordError
|
||||
MGPipeFieldOwnership::kFatal, // RecordError
|
||||
};
|
||||
inline constexpr const char* kMGPipeFieldOwnershipForwardMechanism[kMGPipeFieldOwnershipForwardCount] = {
|
||||
"a server-side binding-point table", // GetBufferBindingPointCount, retires in -
|
||||
"a client-resolved program handle table (ARCHITECTURE 3.2 explicitly-not-ported)", // GetProgramObject, retires in P9
|
||||
"a server-side texture handle table", // GetTextureObject, retires in P7
|
||||
"server-side XFB span state", // HasOpenTransformFeedbackSpan, retires in -
|
||||
"a client-resolved program name probe", // ValidateProgramName, retires in P9
|
||||
"DELETED under split by P5c ev; the re-arriving caps snapshot IS the invalidation (R-12)", // InvalidateCompileEnv, retires in -
|
||||
"OnGlError, the ordered reverse-channel error post (R-12); its ordering is P9's", // RecordError, retires in P9
|
||||
"fixed indexed binding-point capacity; no client lookup", // GetBufferBindingPointCount, retires in -
|
||||
"program identity is resolved on the client and crosses as a shader-CSO handle", // GetProgramObject, retires in -
|
||||
"texture identity crosses as a handle; no server GL-name lookup into the client", // GetTextureObject, retires in -
|
||||
"server Begin/End snapshot table keyed by XFB lifetime id", // HasOpenTransformFeedbackSpan, retires in -
|
||||
"validation belongs to the client; the server validates the carried handle", // ValidateProgramName, retires in -
|
||||
"transport caps republication replaces direct frontend invalidation", // InvalidateCompileEnv, retires in -
|
||||
"transport OnGlError posts an event before the legacy frontend-error-queue arm", // RecordError, retires in -
|
||||
};
|
||||
|
||||
// CONTRACT-P5.md section 3: "70 rows, each in exactly one class".
|
||||
@@ -323,7 +323,7 @@ inline constexpr SizeT kMGPipeVerbBoundaryExemptCount = 3;
|
||||
// wait. A verb with no stamp row admits nothing - the server stamps only MGP_VERB_OP_LIST's
|
||||
// ops, so no other verb can be the one a residual pull is reported against.
|
||||
inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = {
|
||||
{{0x1808402000000040ull, 0x0000000000000000ull}}, // DrawArrays
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawArrays
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElements
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElementsBaseVertex
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // MultiDrawArrays
|
||||
@@ -343,7 +343,7 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = {
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawArraysInstanced
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawElementsIndirect
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DrawArraysIndirect
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // Clear
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // Clear
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferfi
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferfv
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearBufferuiv
|
||||
@@ -352,27 +352,27 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = {
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferfi
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferiv
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClearNamedFramebufferuiv
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // BlitFramebuffer
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BlitFramebuffer
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BlitNamedFramebuffer
|
||||
{{0x1800c02000300080ull, 0x0000000000000000ull}}, // CopyTexImage2D
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTexImage2D
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyTexSubImage2D
|
||||
{{0x1800c02000300080ull, 0x0000000000000000ull}}, // CopyImageSubData
|
||||
{{0x1800c02000300080ull, 0x0000000000000000ull}}, // GenerateMipmap
|
||||
{{0x1800c02000300040ull, 0x0000000000000000ull}}, // ReadPixels
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // CopyImageSubData
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GenerateMipmap
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ReadPixels
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetTexImage
|
||||
{{0x1800c02000300040ull, 0x0000000000000000ull}}, // GetTextureImage
|
||||
{{0x1800402000000040ull, 0x0000000000000000ull}}, // DispatchCompute
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetTextureImage
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DispatchCompute
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DispatchComputeIndirect
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // MemoryBarrier
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // MemoryBarrier
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // MemoryBarrierByRegion
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // BindImageTexture
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BindImageTexture
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetIntegeri_v
|
||||
{{0x1800c02000300000ull, 0x0000000000000000ull}}, // ShaderStorageBlockBinding
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // FenceSync
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // ClientWaitSync
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // WaitSync
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // DeleteSync
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // GetSyncStatus
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ShaderStorageBlockBinding
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // FenceSync
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ClientWaitSync
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // WaitSync
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DeleteSync
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetSyncStatus
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // IsTimerQuerySupported
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BeginTimeElapsedQuery
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // EndTimeElapsedQuery
|
||||
@@ -384,17 +384,17 @@ inline constexpr MGPipeFieldMask kMGPipeAdmittedPullMask[kMGPipeVerbCount] = {
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // EndOcclusionQuery
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BeginXfbPrimitivesQuery
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // EndXfbPrimitivesQuery
|
||||
{{0x1800402000000000ull, 0x0000000000000000ull}}, // PatchParameteri
|
||||
{{0x1808402000000080ull, 0x0000000000000000ull}}, // BeginTransformFeedback
|
||||
{{0x1808402000000080ull, 0x0000000000000000ull}}, // EndTransformFeedback
|
||||
{{0x1808402000000080ull, 0x0000000000000000ull}}, // PauseTransformFeedback
|
||||
{{0x1808402000000080ull, 0x0000000000000000ull}}, // ResumeTransformFeedback
|
||||
{{0x1808402000000080ull, 0x0000000000000000ull}}, // BindTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // PatchParameteri
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BeginTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // EndTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // PauseTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // ResumeTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // BindTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // DeleteTransformFeedback
|
||||
{{0x0000000000000000ull, 0x0000000000000000ull}}, // GetGpuTimestampNs
|
||||
};
|
||||
|
||||
inline constexpr SizeT kMGPipeAdmittedPullPairCount = 128;
|
||||
inline constexpr SizeT kMGPipeAdmittedPullPairCount = 0;
|
||||
|
||||
// THE PREDICATE THE STRICT KNOB ASKS (ID-117). A 69 x 128-bit table, so this costs a shift and
|
||||
// a test on the apply thread - the same shape MGPipeFieldMaskHas already has.
|
||||
@@ -429,6 +429,6 @@ static_assert(MGPipeEveryAdmittedPullIsADebtOfItsVerbsClass(),
|
||||
// The class sizes, as constants a test can pin without recounting the table.
|
||||
inline constexpr SizeT kMGPipeRecordSuppliedFieldCount = 41;
|
||||
inline constexpr SizeT kMGPipeApplierDerivedFieldCount = 6;
|
||||
inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 13;
|
||||
inline constexpr SizeT kMGPipeFatalFieldCount = 3;
|
||||
inline constexpr SizeT kMGPipeBarrierPulledFieldCount = 0;
|
||||
inline constexpr SizeT kMGPipeFatalFieldCount = 16;
|
||||
static_assert(kMGPipeRecordSuppliedFieldCount + kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount == kMGPipeInputFieldCount, "the four class sizes do not partition the field set");
|
||||
|
||||
@@ -230,111 +230,46 @@ TEST_F(FieldOwnershipTest, EveryBarrierPulledRowNamesTheRetiringPhase) {
|
||||
pulled += isPulled ? 1 : 0;
|
||||
}
|
||||
EXPECT_EQ(pulled, kMGPipeBarrierPulledFieldCount);
|
||||
EXPECT_EQ(pulled, SizeT{0}) << "P5f exit forbids reintroducing a residual pull";
|
||||
}
|
||||
|
||||
// The 21 the reduced path actually reads (scout-unmigrated-census section 3: the union of
|
||||
// kClear's 7, kDraw's 19 and kReadback's 12) - AS P5c rv LEFT THEM (CONTRACT-P5C.md §5.3):
|
||||
// NINE moved to RECORD-SUPPLIED through set_context_values (the two texture-unit counters,
|
||||
// the touched-count array, the five XFB values) plus GetCurrentVertexAttribute through the
|
||||
// amended set_vertex_attrib_defaults payload, and the three texture shutters moved to
|
||||
// APPLIER-DERIVED ("a shutter, not a value: the server answers from its own Serial"). What
|
||||
// remains BARRIER-PULLED is EXACTLY the object class - nine non-sticky fields whose storage
|
||||
// is a frontend heap reference no record can carry - plus GetPixelStoreParameters, whose
|
||||
// PACK half the applier writes and whose UNPACK half has no carrier and no backend reader at
|
||||
// all.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) {
|
||||
const MGPipeInputField pulled[] = {
|
||||
MGPipeInputField::GetBoundVertexArray,
|
||||
MGPipeInputField::GetBufferBindingSlot,
|
||||
MGPipeInputField::GetBufferBindingPoint,
|
||||
MGPipeInputField::GetFramebufferBindingSlot,
|
||||
MGPipeInputField::GetImageTextureBinding,
|
||||
MGPipeInputField::GetTextureUnitObject,
|
||||
MGPipeInputField::GetProgramForDraw,
|
||||
MGPipeInputField::GetProgramForDispatch,
|
||||
MGPipeInputField::GetTransformFeedbackProgram,
|
||||
};
|
||||
for (const auto field : pulled) {
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kBarrierPulled)
|
||||
<< kMGPipeInputFieldNames[Index(field)] << " left the reduced path's debt";
|
||||
}
|
||||
// rv's exit line, pinned as a SET and not only as nine rows: the non-sticky
|
||||
// BARRIER-PULLED list above is ALL the non-sticky debt - value-class membership is zero
|
||||
// (CONTRACT-P5C.md §7 table 2) - and the only other BARRIER-PULLED rows are six of the
|
||||
// seven sticky forwards (the seventh, InvalidateCompileEnv, is FATAL since P5c ev).
|
||||
const MGPipeInputField pulledSticky[] = {
|
||||
MGPipeInputField::GetProgramObject,
|
||||
MGPipeInputField::GetTextureObject,
|
||||
MGPipeInputField::ValidateProgramName,
|
||||
// Historical name retained for G14. These are retired legacy getters, not records.
|
||||
const MGPipeInputField retired[] = {
|
||||
MGPipeInputField::GetBoundVertexArray, MGPipeInputField::GetBufferBindingSlot,
|
||||
MGPipeInputField::GetBufferBindingPoint, MGPipeInputField::GetFramebufferBindingSlot,
|
||||
MGPipeInputField::GetImageTextureBinding, MGPipeInputField::GetTextureUnitObject,
|
||||
MGPipeInputField::GetProgramForDraw, MGPipeInputField::GetProgramForDispatch,
|
||||
MGPipeInputField::GetTransformFeedbackProgram, MGPipeInputField::GetProgramObject,
|
||||
MGPipeInputField::GetTextureObject, MGPipeInputField::ValidateProgramName,
|
||||
MGPipeInputField::RecordError,
|
||||
};
|
||||
SizeT pulledCount = 0;
|
||||
for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) {
|
||||
const auto field = static_cast<MGPipeInputField>(i);
|
||||
if (kMGPipeFieldOwnership[i] != MGPipeFieldOwnership::kBarrierPulled) continue;
|
||||
++pulledCount;
|
||||
Bool named = false;
|
||||
for (const auto f : pulled) named = named || field == f;
|
||||
for (const auto f : pulledSticky) named = named || field == f;
|
||||
EXPECT_TRUE(named) << kMGPipeInputFieldNames[i]
|
||||
<< " is BARRIER-PULLED and not in the pinned object-class list";
|
||||
}
|
||||
EXPECT_EQ(pulledCount, 13u) << "9 non-sticky object rows + 4 sticky forwards";
|
||||
EXPECT_EQ(pulledCount, kMGPipeBarrierPulledFieldCount);
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters),
|
||||
for (const auto field : retired)
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kFatal)
|
||||
<< kMGPipeInputFieldNames[Index(field)] << " revived a client-memory accessor";
|
||||
EXPECT_EQ(kMGPipeBarrierPulledFieldCount, SizeT{0});
|
||||
EXPECT_EQ(kMGPipeAdmittedPullPairCount, SizeT{0});
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 0),
|
||||
MGPipeFieldOwnership::kApplierDerived);
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 0u),
|
||||
MGPipeFieldOwnership::kApplierDerived);
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1u),
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1),
|
||||
MGPipeFieldOwnership::kFatal);
|
||||
|
||||
// The two off the reduced path, each for its own checkable reason. THREE UNTIL P5b: the
|
||||
// third was GetProgramForDispatch, FATAL because "there is no compute on the reduced path",
|
||||
// and package i1 is what put compute on the path (CONTRACT-P5B.md §6.9). It is asserted
|
||||
// below in its new class rather than deleted from this case, because a field that quietly
|
||||
// left the FATAL list is exactly what this case exists to catch.
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetBoundTransformFeedbackName),
|
||||
MGPipeFieldOwnership::kFatal);
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetTransformFeedbackPausedPrimitiveCounter),
|
||||
MGPipeFieldOwnership::kFatal);
|
||||
// P5b i1: launch_grid (60) crosses, the backend's PrepareForCompute pulls the compute
|
||||
// program inside it (DirectGLES.cpp:5779), and the field takes GetProgramForDraw's class
|
||||
// and its retiring phases - so it is a measured DEBT now, not a defect.
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch),
|
||||
MGPipeFieldOwnership::kBarrierPulled);
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDispatch),
|
||||
MGPipeFieldOwnershipOf(MGPipeInputField::GetProgramForDraw))
|
||||
<< "GetProgramForDispatch is GetProgramForDraw's twin and must share its class";
|
||||
|
||||
// AND THE NINE rv RETIRED, asserted in their NEW classes rather than deleted: a row that
|
||||
// quietly fell back to BARRIER-PULLED is exactly what this list is for.
|
||||
const MGPipeInputField suppliedByContextValues[] = {
|
||||
MGPipeInputField::GetActiveTextureUnit,
|
||||
MGPipeInputField::GetMaxTouchedTextureUnit,
|
||||
MGPipeInputField::GetTouchedBufferBindingPointCount,
|
||||
MGPipeInputField::IsTransformFeedbackActive,
|
||||
MGPipeInputField::IsTransformFeedbackPaused,
|
||||
MGPipeInputField::GetTransformFeedbackGeneration,
|
||||
MGPipeInputField::GetBoundTransformFeedbackLifetimeId,
|
||||
MGPipeInputField::GetTransformFeedbackCapturedVertices,
|
||||
};
|
||||
for (const auto field : suppliedByContextValues) {
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kRecordSupplied)
|
||||
<< kMGPipeInputFieldNames[Index(field)] << " no longer rides set_context_values";
|
||||
}
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetCurrentVertexAttribute),
|
||||
MGPipeFieldOwnership::kRecordSupplied)
|
||||
<< "the amended set_vertex_attrib_defaults payload carries all three views";
|
||||
const MGPipeInputField shutters[] = {
|
||||
MGPipeInputField::GetSamplingResolutionGeneration,
|
||||
MGPipeInputField::GetTextureBindGeneration,
|
||||
MGPipeInputField::GetTextureContextId,
|
||||
};
|
||||
for (const auto field : shutters) {
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kApplierDerived)
|
||||
<< kMGPipeInputFieldNames[Index(field)]
|
||||
<< " is a shutter: the server answers from its own Serial";
|
||||
}
|
||||
for (const auto field : {MGPipeInputField::GetBufferBindingPointCount,
|
||||
MGPipeInputField::HasOpenTransformFeedbackSpan,
|
||||
MGPipeInputField::GetTextureBindGeneration,
|
||||
MGPipeInputField::GetSamplingResolutionGeneration,
|
||||
MGPipeInputField::GetTextureContextId})
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kApplierDerived);
|
||||
for (const auto field : {MGPipeInputField::GetActiveTextureUnit,
|
||||
MGPipeInputField::GetMaxTouchedTextureUnit,
|
||||
MGPipeInputField::GetTouchedBufferBindingPointCount,
|
||||
MGPipeInputField::IsTransformFeedbackActive,
|
||||
MGPipeInputField::IsTransformFeedbackPaused,
|
||||
MGPipeInputField::GetTransformFeedbackGeneration,
|
||||
MGPipeInputField::GetBoundTransformFeedbackLifetimeId,
|
||||
MGPipeInputField::GetTransformFeedbackCapturedVertices,
|
||||
MGPipeInputField::GetCurrentVertexAttribute})
|
||||
EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kRecordSupplied);
|
||||
}
|
||||
|
||||
TEST_F(FieldOwnershipTest, TheSevenStickyForwardsAgreeWithTheirFieldRows) {
|
||||
@@ -392,145 +327,19 @@ TEST_F(FieldOwnershipTest, VerbBoundaryOpsCoverEveryVerbShapedCall) {
|
||||
EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::GetCaps), MGPipeVerb::kVerbCount);
|
||||
}
|
||||
|
||||
// P5e (gl), ID-116 + ID-125: THE §7 ALLOWLIST IS A DERIVATION, RESTATED HERE INDEPENDENTLY.
|
||||
//
|
||||
// The generator emits kMGPipeAdmittedPullMask by joining four columns; the header's own
|
||||
// static_assert can only check two of them, because the wait column and the stamp map live in
|
||||
// other generated files. This case walks those and asserts the rule end to end. A derivation
|
||||
// that quietly dropped a term would still produce a plausible table - which is precisely how the
|
||||
// hand-kept copy in test.yml came to admit GetTextureObject@CopyImageSubData while omitting
|
||||
// GetFramebufferBindingSlot@ReadPixels.
|
||||
//
|
||||
// THE RULE (ID-125). Admitted iff the field is BARRIER-PULLED, the verb has a stamp row, the
|
||||
// field is inside the verb class's may-read mask, and EITHER the verb's op is statically
|
||||
// barriered OR the field's retiring phase does not name P5e. The second disjunct is the honest
|
||||
// statement of ID-84: this table is consulted only on a record the server already stamped
|
||||
// BARRIERED - CountBarrierPull's unbarriered arm is [[noreturn]] and fires first - so the pull
|
||||
// is legal by construction and the only open question is whether THIS phase still owes the
|
||||
// migration. The retiring-phase column is exactly that answer.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, TheAdmittedPullTableIsID84sDerivationAndNotAList) {
|
||||
// "Does this row's retiring phase name P5e", tokenised rather than substring-matched for
|
||||
// the generator's reason: a "P5" or a future "P5e2" must not answer for "P5e".
|
||||
const auto owedByThisPhase = [](const char* retires) {
|
||||
const std::string phase(retires == nullptr ? "" : retires);
|
||||
for (std::size_t at = phase.find("P5e"); at != std::string::npos;
|
||||
at = phase.find("P5e", at + 1)) {
|
||||
const Bool leftOk = at == 0 || !std::isalnum(static_cast<unsigned char>(phase[at - 1]));
|
||||
const std::size_t after = at + 3;
|
||||
const Bool rightOk = after >= phase.size() ||
|
||||
!std::isalnum(static_cast<unsigned char>(phase[after]));
|
||||
if (leftOk && rightOk) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// ---- ID-125's survivors, each named with the disjunct that carries it ----------------
|
||||
// Disjunct 1, and the largest survivor in the lane (21 entries) - the row the hand-kept
|
||||
// copy omitted. The field IS this phase's debt; read_pixels is kWaitReply, so the client
|
||||
// is parked behind the record and P5C's semantics hold for it.
|
||||
EXPECT_TRUE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetFramebufferBindingSlot,
|
||||
MGPipeVerb::ReadPixels));
|
||||
EXPECT_TRUE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetTextureUnitObject,
|
||||
MGPipeVerb::CopyTexImage2D));
|
||||
// Disjunct 2: a debt a LATER phase owes, on a verb this phase does not barrier.
|
||||
// CONTRACT-P5E §5.7 rules transform feedback out of P5e entirely and this row retires in
|
||||
// P3b/P4b, so the static rule alone would have failed the lane on a row no package in this
|
||||
// phase is allowed to touch.
|
||||
EXPECT_TRUE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetTransformFeedbackProgram,
|
||||
MGPipeVerb::DrawArrays))
|
||||
<< "the lane would be red on a transform-feedback row CONTRACT-P5E §5.7 rules out of "
|
||||
"this phase; ID-125's second disjunct is what admits it";
|
||||
EXPECT_TRUE(MGPipeBarrierPullAdmitted(MGPipeInputField::ValidateProgramName,
|
||||
MGPipeVerb::ShaderStorageBlockBinding));
|
||||
// BOTH DISJUNCTS, ASSERTED SEPARATELY (ID-118 and ID-125 on one pair). GetTextureObject
|
||||
// retires in P7, so disjunct 2 admits it whatever the wire says - and ID-118 ALSO moved
|
||||
// resource_copy_region to a barriered class, so disjunct 1 holds independently. Putting
|
||||
// that op back to kWaitNone must therefore leave the pair admitted and still be wrong, so
|
||||
// the wait class is asserted here in its own words rather than through the allowlist.
|
||||
EXPECT_TRUE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetTextureObject,
|
||||
MGPipeVerb::CopyImageSubData));
|
||||
EXPECT_NE(MGPipeWaitClassFor(MGPWireOp::ResourceCopyRegion), kWaitNone)
|
||||
<< "resource_copy_region is unbarriered again (ID-118): after the flip, a copy record "
|
||||
"reading the client's texture object is an unconditional Fatal - the allowlist still "
|
||||
"admits the pair on the retiring-phase disjunct, so THIS is the assertion that sees it";
|
||||
EXPECT_FALSE(owedByThisPhase(
|
||||
kMGPipeFieldRetiringPhase[static_cast<SizeT>(MGPipeInputField::GetTextureObject)]));
|
||||
|
||||
// ---- and the two rows THIS phase owes stay rejected ---------------------------------
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetProgramForDraw,
|
||||
MGPipeVerb::DrawArrays))
|
||||
<< "the draw path's pull became admitted - the allowlist is now forgiving the lane "
|
||||
"entries this phase is being run to fix";
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetBoundVertexArray,
|
||||
MGPipeVerb::DrawArrays));
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetProgramForDispatch,
|
||||
MGPipeVerb::DispatchCompute));
|
||||
// Magma's row: GetFramebufferBindingSlot retires in P5e on Espryt and P7 on Magma, and the
|
||||
// phase column is ONE string for both, so the derivation cannot admit it on the unbarriered
|
||||
// Clear verb. That is deliberate rather than a gap - the DirectVulkan entries carry their
|
||||
// own expected-red lane label instead, so the Espryt allowlist stays a statement about
|
||||
// Espryt (this package's item 7).
|
||||
// P5f retires every production debt row. Generator self-tests retain synthetic
|
||||
// debt fixtures to exercise all historical admission disjuncts independently.
|
||||
EXPECT_EQ(kMGPipeBarrierPulledFieldCount, SizeT{0});
|
||||
EXPECT_EQ(kMGPipeAdmittedPullPairCount, SizeT{0});
|
||||
for (SizeT v = 0; v < kMGPipeVerbCount; ++v)
|
||||
for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f)
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(static_cast<MGPipeInputField>(f),
|
||||
static_cast<MGPipeVerb>(v)))
|
||||
<< kMGPipeInputFieldNames[f] << "@" << kMGPipeVerbNames[v];
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetFramebufferBindingSlot,
|
||||
MGPipeVerb::Clear));
|
||||
// A field that is not a debt at all is never admitted, on any verb.
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetRenderStateParameters,
|
||||
MGPipeVerb::ReadPixels));
|
||||
// An out-of-range verb answers false rather than reading off the end: CountBarrierPull is
|
||||
// reached from a stamp, and a stamp is only as good as the op that produced it.
|
||||
EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetFramebufferBindingSlot,
|
||||
MGPipeVerb::kVerbCount));
|
||||
|
||||
// ---- THE RULE, RESTATED OVER THE WHOLE TABLE ----------------------------------------
|
||||
// The stamp map is INVERTED here rather than assumed: only an op MGPipeVerbForWireOp names
|
||||
// can ever be the verb a pull is reported against.
|
||||
Bool stamped[kMGPipeVerbCount] = {};
|
||||
Bool waitedFor[kMGPipeVerbCount] = {};
|
||||
for (SizeT op = 0; op < static_cast<SizeT>(MGPWireOp::kOpCount); ++op) {
|
||||
const MGPipeVerb verb = MGPipeVerbForWireOp(static_cast<MGPWireOp>(op));
|
||||
if (verb == MGPipeVerb::kVerbCount) continue;
|
||||
stamped[static_cast<SizeT>(verb)] = true;
|
||||
if (MGPipeWaitClassFor(static_cast<MGPWireOp>(op)) != kWaitNone) {
|
||||
waitedFor[static_cast<SizeT>(verb)] = true;
|
||||
}
|
||||
}
|
||||
SizeT admitted = 0;
|
||||
SizeT byWaitClass = 0;
|
||||
SizeT byRetiringPhase = 0;
|
||||
for (SizeT v = 0; v < kMGPipeVerbCount; ++v) {
|
||||
const auto verb = static_cast<MGPipeVerb>(v);
|
||||
for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) {
|
||||
const auto field = static_cast<MGPipeInputField>(f);
|
||||
if (!MGPipeBarrierPullAdmitted(field, verb)) continue;
|
||||
++admitted;
|
||||
if (waitedFor[v]) ++byWaitClass;
|
||||
if (!owedByThisPhase(kMGPipeFieldRetiringPhase[f])) ++byRetiringPhase;
|
||||
EXPECT_TRUE(stamped[v])
|
||||
<< kMGPipeInputFieldNames[f] << "@" << kMGPipeVerbNames[v]
|
||||
<< " is admitted on a verb the server never stamps, so it is a row of the "
|
||||
"allowlist nothing can exercise and nobody can retire";
|
||||
EXPECT_TRUE(waitedFor[v] || !owedByThisPhase(kMGPipeFieldRetiringPhase[f]))
|
||||
<< kMGPipeInputFieldNames[f] << "@" << kMGPipeVerbNames[v]
|
||||
<< " is admitted although this phase owes the row AND no barriered op stamps the "
|
||||
"verb: after the flip that record's read is torn by construction, and "
|
||||
"admitting it turns an unconditional Fatal into a warning line";
|
||||
EXPECT_EQ(kMGPipeFieldOwnership[f], MGPipeFieldOwnership::kBarrierPulled)
|
||||
<< kMGPipeInputFieldNames[f] << " is admitted but is not a BARRIER-PULLED row";
|
||||
EXPECT_TRUE(MGPipeFieldMaskHas(
|
||||
kMGPipeClassFieldMask[static_cast<SizeT>(kMGPipeVerbClass[v])], field))
|
||||
<< kMGPipeInputFieldNames[f] << "@" << kMGPipeVerbNames[v]
|
||||
<< " is admitted but is outside the verb class's may-read mask, so the residual "
|
||||
"fill never copied it for this verb";
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(admitted, kMGPipeAdmittedPullPairCount)
|
||||
<< "the walk and the generator disagree about how many pairs are admitted";
|
||||
EXPECT_GT(admitted, SizeT{0})
|
||||
<< "the admitted set is EMPTY, which reads in the lane as rigour and is blindness: "
|
||||
"every barriered readback row would be reported as a fresh defect";
|
||||
// BOTH DISJUNCTS CARRY REAL WEIGHT. If either count were zero the rule would have collapsed
|
||||
// to the other one and a whole class of rows would be silently mis-classified.
|
||||
EXPECT_GT(byWaitClass, SizeT{0}) << "no pair is admitted by its verb's wait class";
|
||||
EXPECT_GT(byRetiringPhase, SizeT{0}) << "no pair is admitted by its retiring phase";
|
||||
MGPipeVerb::kVerbCount));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------
|
||||
@@ -779,29 +588,38 @@ TEST_F(FieldOwnershipTest, SetContextValuesLandsInPipeInputsAndTheShuttersAnswer
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0});
|
||||
}
|
||||
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, ABarrierPulledReadAfterAServerStampIsCountedNotFatal) {
|
||||
// P5c rv: the exemplars are OBJECT-class now - the value rows this case used to read
|
||||
// (GetActiveTextureUnit / GetTextureContextId / GetMaxTouchedTextureUnit) are
|
||||
// RECORD-SUPPLIED / APPLIER-DERIVED since rv, and reading them here would count nothing.
|
||||
// The three below are all of kDraw's class, all SharedPtr reads, and all safe on
|
||||
// never-filled storage.
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
ASSERT_EQ(MGPipeResidualPullCount(), Uint64{0});
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{1});
|
||||
(void)gPipeInputs.GetProgramForDraw();
|
||||
(void)gPipeInputs.GetTransformFeedbackProgram();
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{3});
|
||||
#if MGTEST_HAVE_FORK
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
#else
|
||||
GTEST_SKIP() << "needs fork";
|
||||
#endif
|
||||
}
|
||||
|
||||
// The seven carry no MGP_INPUT_CHECK, so freshness can never reach them; this is the only
|
||||
// thing that puts them in `rsp`.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, AStickyForwardIsCountedAsAResidualPull) {
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::Clear);
|
||||
ASSERT_EQ(MGPipeResidualPullCount(), Uint64{0});
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
(void)gPipeInputs.GetProgramObject(1u);
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{2});
|
||||
#if MGTEST_HAVE_FORK
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::Clear);
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"ValidateProgramName@Clear\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
#else
|
||||
GTEST_SKIP() << "needs fork";
|
||||
#endif
|
||||
}
|
||||
|
||||
// ... and outside a server-stamped verb they are ordinary monolith calls, which is what keeps
|
||||
@@ -824,18 +642,17 @@ TEST_F(FieldOwnershipTest, NothingIsCountedOutsideAServerStampedVerb) {
|
||||
TEST_F(FieldOwnershipTest, TheAppliersOwnClearDisarmsTheStampWithoutTheClientsFill) {
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels);
|
||||
ASSERT_TRUE(gPipeInputs.ServerStampedVerb());
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
ASSERT_EQ(MGPipeResidualPullCount(), Uint64{1});
|
||||
|
||||
MGPipeServerClearVerbBoundary(); // what PipeApplier must call on leaving the applier
|
||||
(void)gPipeInputs.GetActiveTextureUnit();
|
||||
ASSERT_EQ(MGPipeResidualPullCount(), Uint64{0});
|
||||
MGPipeServerClearVerbBoundary();
|
||||
EXPECT_FALSE(gPipeInputs.ServerStampedVerb());
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
gPipeInputs.InvalidateCompileEnv();
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{1}) << "a forward outside a stamped verb was counted";
|
||||
EXPECT_EQ(MGPipeResidualPullCount(), Uint64{0});
|
||||
}
|
||||
|
||||
// The verb's own may-read table still holds on the server: kClear does not read
|
||||
// GetProgramForDraw, so reading it there is a stale answer rather than a residual pull,
|
||||
// GetProgramForDraw, so reading it there is a forbidden legacy access,
|
||||
// and it stays Fatal. Counting it would trade a loud staleness for a quiet one.
|
||||
// (P5c rv: the exemplar moved - GetActiveTextureUnit is RECORD-SUPPLIED since rv and would
|
||||
// say nothing about the pulled set here.)
|
||||
@@ -843,7 +660,7 @@ TEST_F(FieldOwnershipTest, ABarrierPulledFieldOutsideTheVerbsClassIsStillFatal)
|
||||
#if MGTEST_HAVE_FORK
|
||||
const ChildResult r = RunInChild([] {
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::Clear);
|
||||
(void)gPipeInputs.GetProgramForDraw(); // BARRIER-PULLED, but not in kClear's class
|
||||
(void)gPipeInputs.GetProgramForDraw(); // retired accessor, outside kClear too
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDraw@Clear\"}"), std::string::npos)
|
||||
@@ -859,9 +676,13 @@ TEST_F(FieldOwnershipTest, ResidualPullsReachThePublishedPerFrameCounter) {
|
||||
PS::SetEnabledForTesting(true);
|
||||
PS::ResetForTesting();
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
(void)gPipeInputs.GetCurrentVertexAttribute(0);
|
||||
EXPECT_EQ(PS::FrameCalls(PS::CallClass::ResidualPulls), Uint64{0});
|
||||
EXPECT_NE(PS::FormatWindowLine().find(" rsp=0"), std::string::npos) << PS::FormatWindowLine();
|
||||
// Prove the reporting channel did not merely become a constant zero after retirement.
|
||||
PS::AddCalls(PS::CallClass::ResidualPulls, 1);
|
||||
EXPECT_EQ(PS::FrameCalls(PS::CallClass::ResidualPulls), Uint64{1});
|
||||
EXPECT_NE(PS::FormatWindowLine().find("rsp="), std::string::npos) << PS::FormatWindowLine();
|
||||
EXPECT_NE(PS::FormatWindowLine().find(" rsp=1"), std::string::npos) << PS::FormatWindowLine();
|
||||
PS::ResetForTesting();
|
||||
PS::SetEnabledForTesting(false);
|
||||
}
|
||||
@@ -1035,178 +856,73 @@ TEST_F(FieldOwnershipTest, TheClientVerbLeaveWritesTheClientBlockAlone) {
|
||||
|
||||
#if MGTEST_HAVE_FORK
|
||||
|
||||
// R-7.3's proof that the instrumentation can go red. An instrumentation that cannot is
|
||||
// decoration, and the set it counts is not empty. (P5c rv: the exemplar is object-class now -
|
||||
// the value rows this case was written against ride set_context_values and answer
|
||||
// RECORD-SUPPLIED.)
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, StrictErrorsTurnsABarrierPulledReadIntoANamedAbort) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.StrictErrors = true;
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_NE(r.Log.find("BARRIER-PULLED"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("MOBILEGL_IPC_STRICT_ERRORS=1"), std::string::npos) << r.Log;
|
||||
// The strict line names the phase that owes the answer; a strict abort that did not would
|
||||
// leave the reader exactly where the gate found them. P5e re-annotated this row (and the
|
||||
// five object-class rows beside it) from "P8" to the phase that actually retires the pull,
|
||||
// which is why the expected text moved with FieldOwnership.def rather than the case being
|
||||
// re-pointed at another field: the string IS the debt entry, read out of the generated
|
||||
// table, and a case that stopped checking it would let the annotation rot.
|
||||
EXPECT_NE(r.Log.find("retires in P5e (Espryt unbarriered), P7 (Magma)]"), std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, TheSameReadWithoutStrictErrorsSurvivesAndIsCounted) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.StrictErrors = false;
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
if (MGPipeResidualPullCount() != 1) ::_exit(7);
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{"), std::string::npos) << r.Log;
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// P5e (gl) RED-ONCE, ID-128: THE THIRD DISJUNCT, AND IT IS A RUNTIME FACT ABOUT THE RECORD.
|
||||
//
|
||||
// GetBoundVertexArray@DrawArrays is this phase's debt on an unbarriered verb, so the static
|
||||
// table rejects it and strict aborts - that is the case two blocks up, and it must keep doing
|
||||
// that for the ORDINARY draw path vi and mv retired. But the two
|
||||
// DirectGLES.Split.ClientVertexArrayScenario entries pull the same pair on a record the client
|
||||
// IS parked behind: MGPipeBarriered escalates a draw carrying client vertex arrays (ID-83), and
|
||||
// ID-82 puts client arrays outside P5e entirely - under run-ahead the client refuses them by
|
||||
// name and staging is P8's. The read site (SyncClientSideVertexArraysForDrawArrays) aborts with
|
||||
// Fatal{RoleViolation, "MGPipeSlots"} if it is ever applied unbarriered, so the pull is legal.
|
||||
//
|
||||
// ONE PAIR, TWO VERDICTS, DECIDED BY THE RECORD AND NOT BY THE TABLE. That is why the flag is
|
||||
// stamped in ApplyOne beside the barriered stamp rather than folded into the generated table:
|
||||
// no table indexed by (field, verb) can tell these two entries apart.
|
||||
//
|
||||
// THE RED: drop `&& !escalated` from CountBarrierPull's strict arm and this case dies of
|
||||
// SIGABRT on Fatal{UnmigratedPipeInput, "GetBoundVertexArray@DrawArrays"}.
|
||||
//
|
||||
// THE CONTROL IS THE CASE TWO BLOCKS UP, NOT A SECOND CHILD HERE.
|
||||
// StrictErrorsTurnsABarrierPulledReadIntoANamedAbort drives the IDENTICAL pull - same field,
|
||||
// same verb, same knob - with the escalation flag at its `false` default, and asserts the abort.
|
||||
// Reading the log delta of two forks in one case is what a shared log file cannot support.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, AnEscalatedRecordsPullIsAdmittedAndSaysWhy) {
|
||||
const ChildResult escalated = RunInChild([] {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.StrictErrors = true;
|
||||
// What PipeApplier::ApplyOne stamps for a draw whose payload carries kDrawClientArrays:
|
||||
// MGPipeBarriered says barriered, the static wait class of draw_vbo says kWaitNone.
|
||||
MGPipeApplierSetCurrentRecordBarrieredByEscalation(true);
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
if (MGPipeResidualPullCount() != 1) ::_exit(7);
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(escalated, 0)) << DescribeStatus(escalated) << "\n" << escalated.Log;
|
||||
EXPECT_EQ(escalated.Log.find("Fatal{UnmigratedPipeInput"), std::string::npos) << escalated.Log;
|
||||
// THE MARKER SAYS WHICH DISJUNCT, and that is what keeps the lane's comparison exact: a pair
|
||||
// admitted by the table is checkable against `--print-admitted`, one admitted by escalation
|
||||
// is not in any static list and must not widen one.
|
||||
EXPECT_NE(escalated.Log.find("Admitted{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"
|
||||
" [BARRIER-PULLED, ADMITTED-ESCALATED, retires in "),
|
||||
std::string::npos)
|
||||
<< escalated.Log;
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// THE SEVEN STICKY FORWARDS ARE NOT EXEMPT FROM THE DETECTOR, which is what this case has
|
||||
// always been for - it was written as "strict PROMOTES them to a Fatal" because before ID-125
|
||||
// every barrier-pulled read under strict aborted.
|
||||
//
|
||||
// P5e (gl), ID-125 CHANGED THE VERDICT AND NOT THE REACH, and the rename says which. All seven
|
||||
// sticky forwards retire in a LATER phase (P7, P7/P9, P7/P13, P9 - FieldOwnership.def's forward
|
||||
// list; not one names P5e), so on any stamped verb the second disjunct admits them: the record
|
||||
// is barriered, the pull is legal by construction, and the phase that owes the migration is not
|
||||
// this one. The thing that would be a defect is a sticky forward the detector never REACHED -
|
||||
// a read that answered out of gPipeInputs without being counted or named - and that is what is
|
||||
// asserted here, by the counter moving and by the marker naming the field and its phase.
|
||||
//
|
||||
// The Fatal half of the mechanism keeps its own case beside this one:
|
||||
// StrictErrorsTurnsABarrierPulledReadIntoANamedAbort uses GetBoundVertexArray, which IS this
|
||||
// phase's debt on an unbarriered verb and therefore still aborts.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, StrictErrorsReachTheStickyForwardsAndNameThemByField) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.StrictErrors = true;
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
// NOT EXEMPT: the forward went through CountBarrierPull like any field accessor. Without
|
||||
// this, "it did not abort" would be indistinguishable from "it was never detected", and
|
||||
// the sticky set is exactly where the gate is structurally blind if it is.
|
||||
if (MGPipeResidualPullCount() != 1) ::_exit(7);
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{UnmigratedPipeInput"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("Admitted{UnmigratedPipeInput, \"ValidateProgramName@DrawArrays\"} "
|
||||
"[BARRIER-PULLED, ADMITTED, retires in P9]"),
|
||||
std::string::npos)
|
||||
<< "the sticky forward was neither aborted on nor named; a debt nobody prints is a debt "
|
||||
"nobody retires\n"
|
||||
<< r.Log;
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"ValidateProgramName@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// P5e (gl) RED-ONCE, ID-117: THE THIRD STATE, AND IT IS THE SAME FIELD AS THE CASE ABOVE.
|
||||
//
|
||||
// ValidateProgramName@DrawArrays aborts (draw_vbo is kWaitNone: the client will not be parked
|
||||
// behind that record and the read is torn by construction). ValidateProgramName@ReadPixels is
|
||||
// the same sticky forward under a verb whose op IS statically barriered, so the client is
|
||||
// parked, the value is real and ordered, and the debt is P9's rather than this phase's - it
|
||||
// says so and the entry COMPLETES. One field, two verbs, opposite verdicts: that is the whole
|
||||
// of ID-84 in two cases, and before this change the lane could not tell them apart because
|
||||
// strict aborted on both, which made CI's allowlist comparison unreachable code.
|
||||
//
|
||||
// THE RED: delete the `if (!MGPipeBarrierPullAdmitted(...))` guard in CountBarrierPull so the
|
||||
// strict arm is unconditional again, and this case dies of SIGABRT instead of exiting 0.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, AnAdmittedBarrierPullIsLoudOnceAndNotFatal) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.StrictErrors = true;
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::ReadPixels);
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
(void)gPipeInputs.ValidateProgramName(1u);
|
||||
(void)gPipeInputs.ValidateProgramName(2u);
|
||||
// rsp counts every barriered pull, and under strict every surviving barriered pull is an
|
||||
// admitted one - ID-119's reworded pin, from the counter's side.
|
||||
if (MGPipeResidualPullCount() != 3) ::_exit(7);
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{UnmigratedPipeInput"), std::string::npos)
|
||||
<< "an admitted pull aborted: the lane can never be green while a debt this phase "
|
||||
"deliberately leaves standing is fatal\n"
|
||||
<< r.Log;
|
||||
// THE GRAMMAR, VERBATIM. `<field>@<verb>` and the bracketed tail are the Fatal's, character
|
||||
// for character; only the leading tag differs, so every filter written since P5c that
|
||||
// matches `Fatal{UnmigratedPipeInput` still means exactly "red".
|
||||
EXPECT_NE(r.Log.find("MGPipe: Admitted{UnmigratedPipeInput, \"ValidateProgramName@ReadPixels\"}"
|
||||
" [BARRIER-PULLED, ADMITTED, retires in P9]"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
// DEDUPED PER (field, verb): three pulls, one line. An 852-draw frame must not write 852.
|
||||
SizeT lines = 0;
|
||||
for (std::size_t at = r.Log.find("Admitted{UnmigratedPipeInput"); at != std::string::npos;
|
||||
at = r.Log.find("Admitted{UnmigratedPipeInput", at + 1)) {
|
||||
++lines;
|
||||
}
|
||||
EXPECT_EQ(lines, SizeT{1})
|
||||
<< "the admitted marker is not deduped per (field, verb); a frame of draws would write "
|
||||
"one line per pull and the marker census could not be read\n"
|
||||
<< r.Log;
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"ValidateProgramName@ReadPixels\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// A FATAL-class read aborts whatever the knob says: no carrier, and the reduced path never
|
||||
// reads it, so it is a real defect rather than a debt.
|
||||
// P5b i1: the exemplar MOVED. This case used GetProgramForDispatch, which is BARRIER-PULLED
|
||||
// from i1 on (a debt the server serves, not an abort), so it would now assert that a served
|
||||
// read aborts - green for the wrong reason at best. GetTransformFeedbackPausedPrimitiveCounter
|
||||
// is the same statement with a field that is still FATAL: reachable only from class kQuery,
|
||||
// which the reduced path never enters.
|
||||
// RED ONCE BY DOING X: put GetProgramForDispatch back in the FATAL block of FieldOwnership.def
|
||||
// and TheFieldOwnershipTableIsTheContractsTableRow's new kBarrierPulled expectation goes red by
|
||||
// name; swap the field below for GetProgramForDispatch and THIS case goes red instead, because
|
||||
// a barrier-pulled read under a stamp does not abort.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, AFatalClassReadAbortsEvenWithoutStrictErrors) {
|
||||
const ChildResult r = RunInChild([] {
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
@@ -1219,20 +935,17 @@ TEST_F(FieldOwnershipTest, AFatalClassReadAbortsEvenWithoutStrictErrors) {
|
||||
<< r.Log;
|
||||
}
|
||||
|
||||
// And the field that LEFT the FATAL class is served rather than fatal, under the verb that made
|
||||
// it reachable. This is i1's half of the §6.9 grant made checkable: a dispatch stamp plus a read
|
||||
// of the compute program must NOT abort, which is precisely the statement "compute is on the
|
||||
// path now". RED ONCE BY DOING X: revert the FieldOwnership.def row to FATAL and this child
|
||||
// aborts with Fatal{UnmigratedPipeInput, "GetProgramForDispatch@DispatchCompute"}.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, TheComputeProgramIsServedUnderADispatchStampFromP5bOn) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DispatchCompute);
|
||||
(void)gPipeInputs.GetProgramForDispatch();
|
||||
MGPipeServerClearVerbBoundary();
|
||||
});
|
||||
EXPECT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch"), std::string::npos)
|
||||
<< r.Log;
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch@DispatchCompute\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// The argument-keyed row: the pack half is answerable and the unpack half is not, and the
|
||||
@@ -1285,37 +998,30 @@ TEST_F(FieldOwnershipTest, AnUnmigratedEmulationIsFatalUnderARealTransportAndIne
|
||||
<< split.Log;
|
||||
}
|
||||
|
||||
// P5f (f1): THE REHEARSAL'S FATAL ARM, AND THE PROOF IT NEEDS NO OTHER KNOB. Under
|
||||
// MOBILEGL_IPC_ROLE_SPLIT_STATE=1 a BARRIER-PULLED read is Fatal{UnmigratedPipeInput} with
|
||||
// StrictErrors FALSE - the server block has no residual fill to answer from, so the read has
|
||||
// no legal value whatever the strict knob says, and the marker names the knob that armed it.
|
||||
// This is also the red-once for the lane's "no unnamed crashes" gate: the read below is an
|
||||
// O-class SharedPtr field, which without this arm would hand the backend an empty pointer to
|
||||
// dereference.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, DualBlockMakesABarrierPulledReadANamedAbortWithoutStrict) {
|
||||
// Historical name retained for G14: the formerly admitted read is now forbidden.
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Transport = MG_Config::TransportMode::InProcess;
|
||||
MG_Config::Ipc.RoleSplitState = true;
|
||||
MG_Config::Ipc.StrictErrors = false;
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
});
|
||||
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"),
|
||||
std::string::npos)
|
||||
<< r.Log;
|
||||
EXPECT_NE(r.Log.find("BARRIER-PULLED"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("MOBILEGL_IPC_ROLE_SPLIT_STATE=1"), std::string::npos) << r.Log;
|
||||
EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log;
|
||||
}
|
||||
|
||||
// ... and the same read with the knob set but the transport MONOLITH is the folded arm: one
|
||||
// block, the pull counted, no Fatal. The knob is not the mechanism; the knob AND a real
|
||||
// transport are.
|
||||
// P5f terminal contract; the historical registration name is retained for G14.
|
||||
TEST_F(FieldOwnershipTest, DualBlockDoesNotArmUnderMonolithTransport) {
|
||||
const ChildResult r = RunInChild([] {
|
||||
MG_Config::Ipc.RoleSplitState = true; // the knob, with no transport behind it
|
||||
MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays);
|
||||
MG_Config::Transport = MG_Config::TransportMode::Monolith;
|
||||
MG_Config::Ipc.RoleSplitState = true;
|
||||
// A real monolith fill, not a manufactured server stamp on a monolith process.
|
||||
MGPipeValidateForVerb(MGPipeVerb::DrawArrays);
|
||||
(void)gPipeInputs.GetBoundVertexArray();
|
||||
if (MGPipeResidualPullCount() != 1) ::_exit(7);
|
||||
if (MGPipeRoleSplitActive() || MGPipeResidualPullCount() != 0) ::_exit(7);
|
||||
});
|
||||
ASSERT_TRUE(ExitedWith(r, 0)) << DescribeStatus(r) << "\n" << r.Log;
|
||||
EXPECT_EQ(r.Log.find("Fatal{"), std::string::npos) << r.Log;
|
||||
|
||||
@@ -748,11 +748,45 @@ def expect_trip(name, because, fn, quiet=False):
|
||||
return 0
|
||||
|
||||
|
||||
def require_no_barrier_pulls(ownership):
|
||||
pulled = sorted(field for field, cls in ownership.items() if cls == "BARRIER_PULLED")
|
||||
if pulled:
|
||||
sys.exit("gen_pipe_field_ownership: P5f forbids production BARRIER_PULLED rows: "
|
||||
+ ", ".join(pulled))
|
||||
|
||||
|
||||
def self_test():
|
||||
"""The negative controls (gen_pipe.py --self-test's shape): each gate must go red for its
|
||||
own reason, and zero trips is itself an error."""
|
||||
coverage = read(COVERAGE_DEF)
|
||||
ownership_text = read(OWNERSHIP_DEF)
|
||||
production_ownership_text = read(OWNERSHIP_DEF)
|
||||
# P5f has no live debt. Exercise admission's historical branches on an explicit
|
||||
# synthetic fixture, rather than requiring real client reads to keep tests alive.
|
||||
ownership_text = production_ownership_text
|
||||
fixture_phases = {
|
||||
"GetBoundVertexArray": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetBufferBindingSlot": "P8/P9/P13",
|
||||
"GetBufferBindingPoint": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetFramebufferBindingSlot": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetImageTextureBinding": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetTextureUnitObject": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetProgramForDraw": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetProgramForDispatch": "P5e (Espryt unbarriered), P7 (Magma)",
|
||||
"GetTransformFeedbackProgram": "P3b/P4b (Espryt), P7 (Magma)",
|
||||
"GetProgramObject": "P9", "GetTextureObject": "P7",
|
||||
"ValidateProgramName": "P9", "RecordError": "P9",
|
||||
}
|
||||
for field, phase in fixture_phases.items():
|
||||
pattern = r'(X\(\s*' + field + r'\s*,)\s*FATAL,\s*"-",'
|
||||
ownership_text, count = re.subn(pattern, lambda m: m[1] + ' BARRIER_PULLED, "' + phase + '",', ownership_text)
|
||||
if count not in (1, 2):
|
||||
sys.exit("gen_pipe_field_ownership: fixture field is not retired: " + field)
|
||||
prefix, forward = ownership_text.split("#define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X)", 1)
|
||||
forward = re.sub(r'(X\(RecordError,\s*BARRIER_PULLED,\s*"P9",\s*)"[^\"]*"',
|
||||
r'\1"OnGlError, synthetic legacy error forward"', forward, count=1)
|
||||
forward = re.sub(r'(X\(GetTextureObject,\s*BARRIER_PULLED,\s*"P7",\s*)"[^\"]*"',
|
||||
r'\1"a server-side texture handle table"', forward, count=1)
|
||||
ownership_text = prefix + "#define MGP_FIELD_OWNERSHIP_FORWARD_LIST(X)" + forward
|
||||
fill = read(PIPE_FILL)
|
||||
accessors, sticky, emitted = parse_coverage(coverage)
|
||||
refused = parse_supplies_whole_field(fill)
|
||||
@@ -951,12 +985,18 @@ def self_test():
|
||||
"to another control - the harness cannot tell one control from another, which is "
|
||||
"exactly the defect that let control #4 be a silent duplicate of control #1")
|
||||
|
||||
controls.append(("a retired field is reintroduced as production debt",
|
||||
"P5f forbids production BARRIER_PULLED rows",
|
||||
lambda: require_no_barrier_pulls(run()[0])))
|
||||
|
||||
trips = 0
|
||||
for name, because, fn in controls:
|
||||
trips += expect_trip(name, because, fn)
|
||||
|
||||
# The positive control: the real tables pass, and they partition the real field set.
|
||||
_, _, _, _, _, counts = run()
|
||||
production = run(own_text=production_ownership_text)
|
||||
require_no_barrier_pulls(production[0])
|
||||
counts = production[-1]
|
||||
total = sum(counts.values())
|
||||
if total != len(accessors):
|
||||
sys.exit("gen_pipe_field_ownership: self-test: the positive control does not partition "
|
||||
@@ -1088,7 +1128,7 @@ def self_test():
|
||||
"its OWN message; harness control OK; positive control OK "
|
||||
"(%d fields partitioned, 7 sticky forwards, 8 refusals)" % (trips, total))
|
||||
print("gen_pipe_field_ownership: self-test: %d admission-derivation control(s) held; the "
|
||||
"derived section 7 allowlist is %d <field>@<verb> pair(s)"
|
||||
"synthetic legacy allowlist is %d pair(s); production has zero debt"
|
||||
% (len(derivation), len(derived)))
|
||||
return 0
|
||||
|
||||
@@ -1115,6 +1155,7 @@ def main():
|
||||
_, exemptMap, required = check_verb_ops(verb_ops, exempt, calls, verbs)
|
||||
ownership, phase, why, forward_map, arg_list, counts = build(
|
||||
accessors, sticky, emitted, refused, rows, forwards, arg_rows)
|
||||
require_no_barrier_pulls(ownership)
|
||||
waits = parse_wait_classes()
|
||||
verb_class, class_mask = parse_verb_classes()
|
||||
admitted_masks, admitted_pairs = build_admitted(
|
||||
|
||||
Reference in New Issue
Block a user