[MG_Backend, MG_Test] (P7): a PH-5 unit control that discriminates the element charge, @Ph-declined on PH-2's eight Fatal sites, and the mip-shadow comment made true

This commit is contained in:
2026-09-23 13:44:37 -04:00
parent 8848ee6ccf
commit 3ef5c61e96
5 changed files with 71 additions and 10 deletions
+4 -4
View File
@@ -3012,7 +3012,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// stops being twinned is the failure mode the refusal exists to replace.
if (res.Slot >= BackendBufferResourceTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"buffer twin request named slot %u, past this table's %u bound",
@@ -3027,7 +3027,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint32 liveGen = g_backendBufferResources.LiveGenAt(res.Slot);
if (liveGen != 0 && liveGen > res.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"buffer twin request named generation %u at slot %u, behind live generation %u",
@@ -4348,7 +4348,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Pipe::MGPipeHandleIsNull(handle)) return false;
if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"%s handle slot %u is past this table's %u bound",
@@ -4363,7 +4363,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint32 liveGen = registry.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"%s handle {%u, %u} names generation behind live generation %u",
+2 -2
View File
@@ -481,7 +481,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Pipe::MGPipeHandleIsNull(handle)) return nullptr;
if (handle.Slot >= SlotTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"GetOrCreateByHandle named slot %u, past this table's %u bound",
@@ -493,7 +493,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint32 liveGen = m_slotTable.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"GetOrCreateByHandle named generation %u at slot %u, behind live generation %u",
+2 -2
View File
@@ -351,7 +351,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED
// PH-2: handle.Slot is peer supplied on the disaggregated arm. Release builds
// must publish a named protocol fault instead of silently returning m_nullTwin.
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"GetOrCreate(handle) named slot %u, past this table's %u bound",
@@ -385,7 +385,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED
// PH-2: a stale peer generation must not silently shed the incumbent twin in a
// release server. The funnel names the exact identity fault and notifies the peer.
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"GetOrCreate(handle) named generation %u at slot %u, behind live generation %u",
@@ -1692,8 +1692,8 @@ void main() {
const Uint64 key = MG_Remote::Server::StagedTextureStore::KeyForTwinAddress(&resource);
for (const auto uploadTarget : uploadTargets) {
for (Uint32 level = baseMipLevel + 1; level < requiredMipLevelCount; ++level) {
// A level the shadow already tracks (an adopted base chain) keeps its bytes;
// the generation made the GPU newer than either, which the mark says.
// Bytes the shadow held for a generated level go: the declaration below moves its
// format and byte bound (NoteLevelDefined clears them); the GPU copy is newer.
// PH-4: a twin-address key never receives a staged run (no Adopt names one),
// so these generated levels are declared BYTELESS - an Unknown format gives a
// zero byte bound and skips the Tex2D device-limit rule the defaults would
@@ -32,7 +32,15 @@
// stays name-for-name identical between the pull and the push trees.
#include <gtest/gtest.h>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <string>
#if !defined(_WIN32)
#include <sys/resource.h>
#include <unistd.h>
#endif
#include "Includes.h"
#if MOBILEGL_PIPE_PUSH
@@ -351,6 +359,59 @@ TEST(ProgramArtifactsCodec, AVectorCountCannotReservePastTheRemainingArchiveByte
#endif
}
// PH-5's DISCRIMINATING control (P7 F2 latch). The case above cannot fail on PH-5's account: its
// count is one element past PH-5's bound, and with TakeCount put back to the old one-byte-per-
// element charge the same count is admitted, resize() is tiny, and the element reads run out of
// bytes - DecodeProgramArtifacts answers false either way (red-once: it stays green). What PH-5
// actually changed is the ALLOCATION a count may cause before the reads, so this case forges the
// count BETWEEN the two bounds - `count == Remaining()`, in an archive padded to 7 MiB - and
// decodes it in a forked child whose address space has 64 MiB of headroom. PH-5 refuses before
// the resize and the child exits 0; the old bound resizes to Remaining() elements of
// sizeof(UniformReflection) each, far past the headroom, and the child dies of std::bad_alloc.
// The same shape crosses the wire in PeerLatchTest's D11ArchiveVectorCount row.
#if MOBILEGL_PIPE_PUSH && !defined(_WIN32)
namespace {
[[noreturn]] void DecodeACountOnlyTheByteBoundAdmitsUnderACapAndExit() {
Vector<Uint8> bytes;
EncodeProgramArtifacts(LinkArtifacts{}, SpirvArtifacts{}, bytes);
constexpr SizeT countOffset = sizeof(Uint32) + sizeof(Uint64);
bytes.resize(SizeT{7} << 20, 0);
const Uint64 remaining = bytes.size() - countOffset - sizeof(Uint64);
std::memcpy(bytes.data() + countOffset, &remaining, sizeof(remaining));
constexpr Uint64 kHeadroom = Uint64{64} << 20;
if (remaining * sizeof(UniformReflection) < 4 * kHeadroom) ::_exit(64); // the case would prove nothing
Uint64 vmKb = 0;
{
std::ifstream status("/proc/self/status");
std::string line;
while (std::getline(status, line)) {
if (line.rfind("VmSize:", 0) == 0) vmKb = std::strtoull(line.c_str() + 7, nullptr, 10);
}
}
if (vmKb == 0) ::_exit(65);
rlimit cap{};
if (::getrlimit(RLIMIT_AS, &cap) != 0) ::_exit(66);
cap.rlim_cur = static_cast<rlim_t>(vmKb * 1024 + kHeadroom);
if (::setrlimit(RLIMIT_AS, &cap) != 0) ::_exit(67);
LinkArtifacts link;
SpirvArtifacts spirv;
const Bool decoded = DecodeProgramArtifacts(bytes.data(), bytes.size(), link, spirv);
::_exit(!decoded && link.uniformReflection.empty() ? 0 : 3);
}
} // namespace
#endif
TEST(ProgramArtifactsCodec, ACountOnlyTheByteBoundAdmitsIsRefusedBeforeItsResize) {
#if MOBILEGL_PIPE_PUSH && !defined(_WIN32)
EXPECT_EXIT(DecodeACountOnlyTheByteBoundAdmitsUnderACapAndExit(), ::testing::ExitedWithCode(0), ".*")
<< "std::bad_alloc escaping the child is the resize PH-5 exists to refuse: the vector count was "
"charged one byte per element instead of sizeof(value_type); exit 3 is a decode that did not "
"refuse; 64+ is the fixture";
#else
GTEST_SKIP() << "MOBILEGL_PIPE_PUSH is off (the archive codec is push-only), or no RLIMIT_AS / fork here";
#endif
}
// Negative control 2: both the version and schema word must refuse mismatches.
// Monolith v1 retains the old native-size echo in that second word.
TEST(ProgramArtifactsCodec, AVersionMismatchIsRefused) {