From 09d2bb11b74c055146b2b5393a8d6a7dbdf80ee9 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sat, 5 Sep 2026 22:52:19 -0400 Subject: [PATCH] [Feat] (Program): add the reflection-archive field tables and sizeof trip wires to ProgramArtifacts.h - One VisitFields table per type (ARCHITECTURE.md:259), a free constrained template so the moved struct bodies stay verbatim and one table serves both the const (serialize) and non-const (deserialize) direction. LinkArtifacts::program is deliberately absent: it is null for every archived instance and must never be serialized. - Trip wires: TypeFacts is pinned at 44 bytes on every ABI; the container-bearing four are pinned per standard library - libstdc++ 64-bit here (128/128/1056/88, measured on this build), the libc++ branch left inert for the integrator to pin from the NDK build, MSVC unasserted. A member added without a table entry changes the size and the assertion message sends the author to the table. - ProgramArtifactsTest counts the tables (20/14/11/57/8; const and non-const walks agree, names distinct), proves constness passes through, and records every sizeof as a ctest property so a new toolchain's numbers are readable from any `ctest -V` log. --- .../GLState/ProgramState/ProgramArtifacts.h | 176 ++++++++++++++++++ .../MG_Test/Program/ProgramArtifactsTest.cpp | 86 +++++++++ 2 files changed, 262 insertions(+) diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h index 4afd48a2..016aa230 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h @@ -397,4 +397,180 @@ namespace MobileGL::MG_State::GLState { // so every query keeps the truthful GL spelling. Bool pointSizeDemoted = false; }; + + // ---- the archive field tables (ARCHITECTURE.md:259): ONE table per type, serving both directions ---- + // + // Visitor contract: v(const char* name, Field&) - Field is const when Self is const, so a + // single table serves the serializer (const) and the deserializer (non-const); `Self` + // deduces either. A visitor recurses into TypeFacts / ResourceReflection / XfbVarying by + // calling VisitFields on the element it was handed; the tables never recurse themselves. + // + // Free constrained templates rather than members so the struct bodies above stay a verbatim + // move. The sizeof trip wires below are what keep these tables honest: a member added to a + // struct changes its size, trips the assertion, and the message sends the author here. + template + requires std::same_as, TypeFacts> + void VisitFields(Self& a, V&& v) { + v("isArray", a.isArray); + v("isSizedArray", a.isSizedArray); + v("isMatrix", a.isMatrix); + v("isVector", a.isVector); + v("isOpaque", a.isOpaque); + v("isTexture", a.isTexture); + v("isImage", a.isImage); + v("isDouble", a.isDouble); + v("isVoid", a.isVoid); + v("isBuffer", a.isBuffer); + v("isPatch", a.isPatch); + v("hasIndex", a.hasIndex); + v("hasFormat", a.hasFormat); + v("vectorSize", a.vectorSize); + v("matrixCols", a.matrixCols); + v("matrixRows", a.matrixRows); + v("layoutIndex", a.layoutIndex); + v("layoutFormat", a.layoutFormat); + v("layoutMatrix", a.layoutMatrix); + v("basicType", a.basicType); + } // 20 fields + + template + requires std::same_as, ResourceReflection> + void VisitFields(Self& a, V&& v) { + v("name", a.name); + v("glDefineType", a.glDefineType); + v("offset", a.offset); + v("size", a.size); + v("index", a.index); + v("counterIndex", a.counterIndex); + v("arrayStride", a.arrayStride); + v("topLevelArraySize", a.topLevelArraySize); + v("topLevelArrayStride", a.topLevelArrayStride); + v("binding", a.binding); + v("location", a.location); + v("stages", a.stages); + v("arraySize", a.arraySize); + v("type", a.type); // visited as a value; the visitor recurses with VisitFields(a.type, v) if it wants to + } // 14 fields + + template + requires std::same_as, XfbVarying> + void VisitFields(Self& a, V&& v) { + v("name", a.name); + v("type", a.type); + v("size", a.size); + v("bufferIndex", a.bufferIndex); + v("offsetBytes", a.offsetBytes); + v("byteSize", a.byteSize); + v("packedOffsetBytes", a.packedOffsetBytes); + v("blockInstanceName", a.blockInstanceName); + v("blockName", a.blockName); + v("blockMemberIndex", a.blockMemberIndex); + v("blockMemberElement", a.blockMemberElement); + } // 11 fields + + // Every member EXCEPT `program`: it is null for every archived instance by construction + // (ProgramTranslationCache.h asserts that at insert) and must never be serialized - it is + // the live glslang TProgram that only DoReflection may touch. 57 of the 58 members. + template + requires std::same_as, LinkArtifacts> + void VisitFields(Self& a, V&& v) { + v("uniformReflection", a.uniformReflection); + v("blockReflection", a.blockReflection); + v("pipeInputReflection", a.pipeInputReflection); + v("pipeOutputReflection", a.pipeOutputReflection); + v("lastStageIsFragment", a.lastStageIsFragment); + v("computeLocalSize", a.computeLocalSize); + v("uniformIndexByName", a.uniformIndexByName); + v("attribs", a.attribs); + v("attribTypes", a.attribTypes); + v("linkedFragDataLocation", a.linkedFragDataLocation); + v("linkedFragDataIndex", a.linkedFragDataIndex); + v("glUniformIndexToTProgram", a.glUniformIndexToTProgram); + v("tProgramUniformIndexToGl", a.tProgramUniformIndexToGl); + v("glBlockIndexToTProgram", a.glBlockIndexToTProgram); + v("tProgramBlockIndexToGl", a.tProgramBlockIndexToGl); + v("glUniformBlockIndexToBlock", a.glUniformBlockIndexToBlock); + v("blockIndexToGlUniformBlock", a.blockIndexToGlUniformBlock); + v("linkedExplicitUniformLocations", a.linkedExplicitUniformLocations); + v("uniformInitialValues", a.uniformInitialValues); + v("uniformLocations", a.uniformLocations); + v("writtenUniformLocationBits", a.writtenUniformLocationBits); + v("writtenUniformIndexBits", a.writtenUniformIndexBits); + v("writtenUniformIndices", a.writtenUniformIndices); + v("uniformIndexInTProgram", a.uniformIndexInTProgram); + v("uniformSamplerOrImageUnitIndex", a.uniformSamplerOrImageUnitIndex); + v("explicitOpaqueUniformBindings", a.explicitOpaqueUniformBindings); + v("uniformBlockIndexByName", a.uniformBlockIndexByName); + v("uniformBlockBinding", a.uniformBlockBinding); + v("shaderStorageBlockBinding", a.shaderStorageBlockBinding); + v("storageBlocksWithoutBinding", a.storageBlocksWithoutBinding); + v("uniformBlocksWithoutBinding", a.uniformBlocksWithoutBinding); + v("activeUniformCount", a.activeUniformCount); + v("usesReservedNumSamples", a.usesReservedNumSamples); + v("maxUniformLocation", a.maxUniformLocation); + v("uniformNameMaxLength", a.uniformNameMaxLength); + v("attribInNameMaxLength", a.attribInNameMaxLength); + v("uniformBlockNameMaxLength", a.uniformBlockNameMaxLength); + v("infoLog", a.infoLog); + v("linkStatus", a.linkStatus); + v("xfbVaryings", a.xfbVaryings); + v("xfbInterfaceNames", a.xfbInterfaceNames); + v("xfbStrides", a.xfbStrides); + v("gsStripTriangles", a.gsStripTriangles); + v("gsStripCaptureFixup", a.gsStripCaptureFixup); + v("gsInputPrimitive", a.gsInputPrimitive); + v("tcsOutputVertices", a.tcsOutputVertices); + v("gsOutputPrimitive", a.gsOutputPrimitive); + v("gsMaxVertices", a.gsMaxVertices); + v("gsInvocations", a.gsInvocations); + v("tessGenMode", a.tessGenMode); + v("tessGenSpacing", a.tessGenSpacing); + v("tessGenVertexOrder", a.tessGenVertexOrder); + v("tessGenPointMode", a.tessGenPointMode); + v("xfbBufferMode", a.xfbBufferMode); + v("xfbVaryingNameMaxLength", a.xfbVaryingNameMaxLength); + v("xfbNeedsScatteredCapture", a.xfbNeedsScatteredCapture); + v("xfbPackedStride", a.xfbPackedStride); + } // 57 fields (58 members minus `program`) + + template + requires std::same_as, SpirvArtifacts> + void VisitFields(Self& a, V&& v) { + v("generatedSpirv", a.generatedSpirv); + v("enableSpirvValidation", a.enableSpirvValidation); + v("uniformOffsets", a.uniformOffsets); + v("globalUboScratch", a.globalUboScratch); + v("reservedNumSamplesOffset", a.reservedNumSamplesOffset); + v("spirvStatus", a.spirvStatus); + v("nativeFloat64", a.nativeFloat64); + v("pointSizeDemoted", a.pointSizeDemoted); + } // 8 fields + + // ---- trip wires ---- + // TypeFacts is a POD on every ABI: 13 Bool + 3 bytes of padding + 7 x 4-byte scalars. + static_assert(std::is_trivially_copyable_v && sizeof(TypeFacts) == 44, + "TypeFacts changed: add the field to VisitFields(TypeFacts) (and its serializer when one exists), then update this number"); + // The container-bearing structs have one size per standard library (std::string and + // std::set differ between libstdc++ and libc++), so their numbers are pinned PER STL: + // libstdc++ (the Linux CI toolchain) here, libc++ (the NDK) by the integrator, MSVC + // unasserted. ProgramArtifactsTest records every sizeof as a ctest property on every + // platform, which is where a new toolchain's numbers are read from. +#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX) +#define MGL_RESOURCEREFLECTION_SIZE 128 +#define MGL_XFBVARYING_SIZE 128 +#define MGL_LINKARTIFACTS_SIZE 1056 +#define MGL_SPIRVARTIFACTS_SIZE 88 +#elif defined(_LIBCPP_VERSION) && (SIZE_MAX == UINT64_MAX) && defined(MGL_ARTIFACT_SIZES_LIBCXX_PINNED) + // The integrator pins these from the NDK build (brief C.4); until then this branch is inert. +#endif +#ifdef MGL_LINKARTIFACTS_SIZE + static_assert(sizeof(ResourceReflection) == MGL_RESOURCEREFLECTION_SIZE, + "ResourceReflection changed size: add the field to VisitFields(ResourceReflection) (and its serializer when one exists), then update this number"); + static_assert(sizeof(XfbVarying) == MGL_XFBVARYING_SIZE, + "XfbVarying changed size: add the field to VisitFields(XfbVarying) (and its serializer when one exists), then update this number"); + static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE, + "LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) (and its serializer when one exists), then update this number"); + static_assert(sizeof(SpirvArtifacts) == MGL_SPIRVARTIFACTS_SIZE, + "SpirvArtifacts changed size: add the field to VisitFields(SpirvArtifacts) (and its serializer when one exists), then update this number"); +#endif } // namespace MobileGL::MG_State::GLState diff --git a/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp b/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp index 78436c45..72727aab 100755 --- a/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp @@ -12,6 +12,9 @@ // For the alias checks only. #include +#include +#include +#include #include namespace { @@ -44,4 +47,87 @@ namespace { EXPECT_EQ(sizeof(TypeFacts), 44u); EXPECT_EQ(alignof(TypeFacts), 4u); } + + // A visitor that counts what a table hands it and checks the names are distinct - the + // archive's one field table per type has to name every member exactly once. + struct CountingVisitor { + std::size_t count = 0; + std::set names; + template + void operator()(const char* name, Field&) { + ++count; + names.insert(name); + } + }; + + template + std::size_t CountFields() { + T value{}; + CountingVisitor mutableVisitor; + VisitFields(value, mutableVisitor); + const T& constValue = value; + CountingVisitor constVisitor; + VisitFields(constValue, constVisitor); + EXPECT_EQ(mutableVisitor.count, constVisitor.count) << "const and non-const walks disagree"; + EXPECT_EQ(mutableVisitor.names.size(), mutableVisitor.count) << "a field name is listed twice"; + EXPECT_EQ(mutableVisitor.names, constVisitor.names); + return mutableVisitor.count; + } + + // The field counts are the member counts of the moved structs (LinkArtifacts minus its + // never-archived `program`). A member added without a table entry is caught by the sizeof + // trip wires in the header; a table entry dropped without a member change is caught here. + TEST(ProgramArtifacts, VisitFieldsCoversEveryMember) { + EXPECT_EQ(CountFields(), 20u); + EXPECT_EQ(CountFields(), 14u); + EXPECT_EQ(CountFields(), 11u); + EXPECT_EQ(CountFields(), 57u); + EXPECT_EQ(CountFields(), 8u); + } + + // A const walk hands the visitor const references, a non-const walk mutable ones: the one + // table really serves both directions. + TEST(ProgramArtifacts, VisitFieldsPassesConstnessThrough) { + LinkArtifacts artifacts; + std::size_t mutableFields = 0; + VisitFields(artifacts, [&](const char*, auto& field) { + static_assert(!std::is_const_v>); + ++mutableFields; + }); + const LinkArtifacts& constArtifacts = artifacts; + std::size_t constFields = 0; + VisitFields(constArtifacts, [&](const char*, auto& field) { + static_assert(std::is_const_v>); + ++constFields; + }); + EXPECT_EQ(mutableFields, constFields); + // The table can write through: a deserializer's shape. + VisitFields(artifacts, [](const char* name, auto& field) { + if constexpr (std::is_same_v, Bool>) { + if (std::string(name) == "linkStatus") field = true; + } + }); + EXPECT_TRUE(artifacts.linkStatus); + } + + // The sizeof numbers, visible in every `ctest -V` log on every platform: this is where the + // integrator reads a new toolchain's values from before pinning them in the header. + TEST(ProgramArtifacts, SizesArePinnedOnThisToolchain) { + RecordProperty("sizeof_TypeFacts", static_cast(sizeof(TypeFacts))); + RecordProperty("sizeof_ResourceReflection", static_cast(sizeof(ResourceReflection))); + RecordProperty("sizeof_XfbVarying", static_cast(sizeof(XfbVarying))); + RecordProperty("sizeof_LinkArtifacts", static_cast(sizeof(LinkArtifacts))); + RecordProperty("sizeof_SpirvArtifacts", static_cast(sizeof(SpirvArtifacts))); + std::printf("sizeof: TypeFacts=%zu ResourceReflection=%zu XfbVarying=%zu LinkArtifacts=%zu SpirvArtifacts=%zu\n", + sizeof(TypeFacts), sizeof(ResourceReflection), sizeof(XfbVarying), sizeof(LinkArtifacts), + sizeof(SpirvArtifacts)); +#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX) + EXPECT_EQ(sizeof(ResourceReflection), static_cast(MGL_RESOURCEREFLECTION_SIZE)); + EXPECT_EQ(sizeof(XfbVarying), static_cast(MGL_XFBVARYING_SIZE)); + EXPECT_EQ(sizeof(LinkArtifacts), static_cast(MGL_LINKARTIFACTS_SIZE)); + EXPECT_EQ(sizeof(SpirvArtifacts), static_cast(MGL_SPIRVARTIFACTS_SIZE)); +#else + RecordProperty("sizes_pinned", "no: not the libstdc++ 64-bit toolchain"); +#endif + } } // namespace