From 377aede6eff1cea93a3192afda35104253679de1 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 19 Sep 2026 23:46:36 -0400 Subject: [PATCH] [MG_Pipe, MG_Test, CI] (Disaggregated): P5f close - retire residual pointer accessors and enforce a zero-debt table --- MobileGL/MG_Backend/MGPipe/PipeInputs.cpp | 6 +- MobileGL/MG_Impl/Pipe/PipeFill.cpp | 58 +- MobileGL/MG_Pipe/FieldOwnership.def | 232 ++------ .../MG_Pipe/generated/PipeFieldOwnership.inc | 126 ++-- MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp | 550 ++++-------------- scripts/gen_pipe_field_ownership.py | 47 +- 6 files changed, 292 insertions(+), 727 deletions(-) diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp index d8bcba59..ae13c503 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp @@ -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 diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 07f1cea7..3cb288c8 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -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 - 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, an ImageTextureBinding, a TextureUnit, two - // SharedPtr - 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: diff --git a/MobileGL/MG_Pipe/FieldOwnership.def b/MobileGL/MG_Pipe/FieldOwnership.def index 771bdeb3..30c9bc75 100644 --- a/MobileGL/MG_Pipe/FieldOwnership.def +++ b/MobileGL/MG_Pipe/FieldOwnership.def @@ -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, "@"}. -// -// 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 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; 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; 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 keyed by GL name") \ - X(GetTextureObject, BARRIER_PULLED, "P7", \ - "hands the backend a frontend SharedPtr 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. // diff --git a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc index af763391..fbf256e1 100644 --- a/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc +++ b/MobileGL/MG_Pipe/generated/PipeFieldOwnership.inc @@ -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"); diff --git a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp index e44e643f..5a757075 100644 --- a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp +++ b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp @@ -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(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(phase[at - 1])); - const std::size_t after = at + 3; - const Bool rightOk = after >= phase.size() || - !std::isalnum(static_cast(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(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(f), + static_cast(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(MGPWireOp::kOpCount); ++op) { - const MGPipeVerb verb = MGPipeVerbForWireOp(static_cast(op)); - if (verb == MGPipeVerb::kVerbCount) continue; - stamped[static_cast(verb)] = true; - if (MGPipeWaitClassFor(static_cast(op)) != kWaitNone) { - waitedFor[static_cast(verb)] = true; - } - } - SizeT admitted = 0; - SizeT byWaitClass = 0; - SizeT byRetiringPhase = 0; - for (SizeT v = 0; v < kMGPipeVerbCount; ++v) { - const auto verb = static_cast(v); - for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) { - const auto field = static_cast(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(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. `@` 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; diff --git a/scripts/gen_pipe_field_ownership.py b/scripts/gen_pipe_field_ownership.py index cfe72734..cbb5b452 100644 --- a/scripts/gen_pipe_field_ownership.py +++ b/scripts/gen_pipe_field_ownership.py @@ -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 @ 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(