mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
- All four capability markers are now content probes over the directory the owning package owns,
through one helper. The magma pair still read a single hard-coded VertexInputStateFactory.cpp
while package D already keeps one of its two Features.PipeHandleAbaControl consumers in
Renderer/VulkanRenderer.cpp, so one file move on D's side was a permanent AbaControl skip - the
same defect the CSO probe was rewritten for. The DirectGLES probe stops asking whether
SlotTables.h exists and asks for kMGPipeSubsystemEsprytSlots, the bit the arm is actually gated
on. Every globbed file stays in CMAKE_CONFIGURE_DEPENDS, and the glob is CONFIGURE_DEPENDS.
- Verified: with one throwaway header naming each symbol, build-push configures to "keyed on
{slot, gen}" / "has an emitter" / "has a consumer" and all four MGITEST_* markers appear 24
times in the generated ctest environments; with the headers gone, all four are back to 0 and
the four "will SKIP" verdicts return. The magma sim sat in Renderer/, not in the path the old
probe hard-coded.
- g7_negative_control.sh no longer exits 0 when the control trips for the wrong reason. A
SetterConsistency that had gone red for an unrelated reason satisfied "ctest failed" and never
named SetColorMask, and the integrator's D.3 reads this script's rc. The verdict is now taken
after the restore and the rebuild - a broken build directory is worse than any exit status -
and reported as rc 1 with the output kept, alongside the existing "did not trip" rc 1.
- HandleRecycleScenario writes down what the name-recycle proxy costs: the AbaControl arm asserts
corruption that needs the heap BLOCK back, sees only the NAME, and so can red an always-on
integration-gpu lane for an allocator reason. That trade is deliberate - the alternative is an
arm that is green on the day the reproducer stops reproducing - and the consequence is now
written both in the header and at the skip that is the last thing standing between the two.
264 lines
13 KiB
Bash
264 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|
# G7's negative control: break the pipeline/dynamic split on purpose and prove the
|
|
# setter-consistency test says so.
|
|
#
|
|
# WHAT G7 CLAIMS. MG_Pipe/MGPipeRenderStateSpans.h partitions RenderStateParameters into a
|
|
# pipeline half and a dynamic half by one rule - a byte is pipeline if and only if some public
|
|
# RenderState setter that calls BumpVersions() writes it - and
|
|
# MG_Test/Pipe/RenderStateSpansTest.cpp walks EVERY setter asserting that the pipeline-subset
|
|
# hash moves exactly when GetPipelineStateVersion() moves.
|
|
#
|
|
# WHY A CONTROL IS NEEDED AT ALL. That test is green on a correct table, and it would also be
|
|
# green on a table it had stopped looking at: a walk that silently drove no setters, a hash that
|
|
# stopped depending on the chunks, an assertion someone loosened. Green tells you nothing about
|
|
# whether the test can still fail. This script makes it fail, for the one reason it exists to
|
|
# catch, and reports a NON-zero ctest THAT NAMES THE DEMOTED MEMBER'S SETTER as the pass. A red
|
|
# for any other reason is reported as inconclusive (rc 1), not as a pass: the script knows the
|
|
# difference, so its exit status has to carry it.
|
|
#
|
|
# THE BREAK. ColorMasks is moved out of pipeline chunk P1 into a dynamic chunk of its own, by
|
|
# inserting two boundaries - at ColorMasks and at FramebufferSrgbEnabled - into the boundary
|
|
# table. That is deliberately a break the compiler CANNOT catch on its own: the chunks still
|
|
# ascend, still do not overlap and still cover [0, sizeof(RenderStateParameters)) exactly, so
|
|
# every structural static_assert in the header still holds. What breaks is the meaning:
|
|
# glColorMask bumps m_pipelineStateVersion but no longer moves the pipeline-subset hash, and
|
|
# SetterConsistency has to name SetColorMask.
|
|
#
|
|
# The four measurement pins (7 / 8 chunks, 396 / 772 bytes) are relaxed by the same patch,
|
|
# because they pin the SHIPPED table rather than the invariant - leaving them would turn this
|
|
# into a build break, which proves the assertions compile rather than that the test still checks.
|
|
#
|
|
# WHY IT IS NOT A CI LANE. It rebuilds the library twice. It is run by hand, and by the
|
|
# integrator at the P2 five-part gate.
|
|
#
|
|
# Usage:
|
|
# scripts/g7_negative_control.sh <build-dir> [--verify-patch-only]
|
|
#
|
|
# <build-dir> a configured build directory carrying the push-only unit tests
|
|
# (MGPipeRenderStateSpans.cpp is compiled only under MOBILEGL_PIPE_PUSH,
|
|
# so a pull build has neither the table nor the test)
|
|
# --verify-patch-only apply the patch, rebuild, report whether it still compiles, and
|
|
# revert - WITHOUT requiring the test to exist. This is the mechanism
|
|
# check, not the control; it never reports the control as passed.
|
|
#
|
|
# Exit codes: 0 the control tripped AND named SetColorMask (or, under --verify-patch-only, the
|
|
# patch compiled);
|
|
# 1 the control did not answer: either the test stayed green on a demoted member, or
|
|
# it went red without ever naming SetColorMask, so the red cannot be attributed to
|
|
# the demotion. Both are findings about the test, not errors in this script - and
|
|
# both leave the tree restored and rebuilt;
|
|
# 2 the script could not run the control at all (bad arguments, missing test,
|
|
# a build that was already broken, a failed restore).
|
|
set -u -o pipefail
|
|
|
|
BUILD_DIR=""
|
|
PATCH_ONLY=0
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--verify-patch-only) PATCH_ONLY=1; shift ;;
|
|
-*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
*) BUILD_DIR=$1; shift ;;
|
|
esac
|
|
done
|
|
[ -n "$BUILD_DIR" ] || { echo "usage: $0 <build-dir> [--verify-patch-only]" >&2; exit 2; }
|
|
|
|
REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
cd "$REPO_ROOT" || exit 2
|
|
[ -f "$BUILD_DIR/CMakeCache.txt" ] || { echo "$BUILD_DIR is not a configured build directory" >&2; exit 2; }
|
|
|
|
HEADER=MobileGL/MG_Pipe/MGPipeRenderStateSpans.h
|
|
SOURCE=MobileGL/MG_Pipe/MGPipeRenderStateSpans.cpp
|
|
TEST_NAME='RenderStateSpans\.SetterConsistency'
|
|
LOG_DIR=$(mktemp -d)
|
|
BACKUP_DIR="$LOG_DIR/orig"
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
say() { echo "[g7] $*" >&2; }
|
|
|
|
restore() {
|
|
# Restore from the byte-for-byte copies taken before the patch, never from git: a developer
|
|
# running this on a dirty tree must get their tree back, not HEAD.
|
|
if [ -f "$BACKUP_DIR/header" ]; then cp -f "$BACKUP_DIR/header" "$HEADER"; fi
|
|
if [ -f "$BACKUP_DIR/source" ]; then cp -f "$BACKUP_DIR/source" "$SOURCE"; fi
|
|
}
|
|
trap 'restore' EXIT
|
|
|
|
cp -f "$HEADER" "$BACKUP_DIR/header" || exit 2
|
|
cp -f "$SOURCE" "$BACKUP_DIR/source" || exit 2
|
|
|
|
# --- 0. the control has to have something to control -----------------------------------------
|
|
# A missing test is NOT a pass. Without this the script would patch, watch ctest match no tests,
|
|
# read that as "the test failed" and report the control as tripped - a green that means the
|
|
# opposite of what it says.
|
|
if [ "$PATCH_ONLY" = 0 ]; then
|
|
matched=$(ctest --test-dir "$BUILD_DIR" -N -R "$TEST_NAME" 2>/dev/null | grep -cE '^ *Test *#[0-9]+:')
|
|
if [ "${matched:-0}" -eq 0 ]; then
|
|
say "no test matches $TEST_NAME in $BUILD_DIR."
|
|
say "That test is P2 package A's (MG_Test/Pipe/RenderStateSpansTest.cpp, commit c2 on p2/spans);"
|
|
say "until it exists this control has nothing to trip and cannot report a pass. Re-run against a"
|
|
say "tree that carries it, or use --verify-patch-only to exercise the patch mechanism alone."
|
|
exit 2
|
|
fi
|
|
say "$matched matching test(s) before the patch"
|
|
fi
|
|
|
|
# --- 1. the tree must be green BEFORE the break ----------------------------------------------
|
|
# Otherwise a red after the patch says nothing: it could have been red already.
|
|
say "building $BUILD_DIR as it is"
|
|
if ! cmake --build "$BUILD_DIR" -j "$(nproc)" > "$LOG_DIR/build-before.log" 2>&1; then
|
|
say "the build is already broken before any patch - see $LOG_DIR/build-before.log"
|
|
tail -20 "$LOG_DIR/build-before.log" >&2
|
|
exit 2
|
|
fi
|
|
if [ "$PATCH_ONLY" = 0 ]; then
|
|
if ! ctest --test-dir "$BUILD_DIR" -R "$TEST_NAME" --no-tests=error --output-on-failure \
|
|
> "$LOG_DIR/ctest-before.log" 2>&1; then
|
|
say "$TEST_NAME is already red before the patch - fix that first, the control proves nothing here"
|
|
tail -30 "$LOG_DIR/ctest-before.log" >&2
|
|
exit 2
|
|
fi
|
|
say "$TEST_NAME is green before the patch"
|
|
fi
|
|
|
|
# --- 2. demote ColorMasks --------------------------------------------------------------------
|
|
say "demoting ColorMasks out of the pipeline half"
|
|
python3 - "$HEADER" "$SOURCE" <<'PY' || exit 2
|
|
import sys
|
|
|
|
header_path, source_path = sys.argv[1], sys.argv[2]
|
|
|
|
|
|
def patch(path, pairs):
|
|
text = open(path, encoding='utf-8').read()
|
|
for old, new in pairs:
|
|
if text.count(old) != 1:
|
|
sys.stderr.write("[g7] cannot patch %s: %d matches for %r\n"
|
|
% (path, text.count(old), old[:70]))
|
|
sys.stderr.write("[g7] the chunk table has been rewritten since this control was "
|
|
"written; update the control, do not delete it.\n")
|
|
sys.exit(1)
|
|
text = text.replace(old, new)
|
|
open(path, 'w', encoding='utf-8', newline='\n').write(text)
|
|
|
|
|
|
# Two extra boundaries split pipeline chunk P1 into
|
|
# [BlendStates, ColorMasks) pipeline (odd index, unchanged parity)
|
|
# [ColorMasks, FramebufferSrgb) DYNAMIC - the demotion
|
|
# [FramebufferSrgb, ClearColor) pipeline
|
|
# Adding exactly TWO boundaries keeps every later chunk's index parity, so the alternating
|
|
# pipeline/dynamic rule still assigns every other chunk the half it had.
|
|
patch(header_path, [
|
|
("inline constexpr SizeT kMGPipeRenderStateChunkCount = 15;",
|
|
"inline constexpr SizeT kMGPipeRenderStateChunkCount = 17; // G7 NEGATIVE CONTROL"),
|
|
(""" offsetof(RenderStateParameters, BlendStates),""",
|
|
""" offsetof(RenderStateParameters, BlendStates),
|
|
// G7 NEGATIVE CONTROL: ColorMasks demoted to a dynamic chunk of its own.
|
|
offsetof(RenderStateParameters, ColorMasks),
|
|
offsetof(RenderStateParameters, FramebufferSrgbEnabled),"""),
|
|
("static_assert(kMGPipePipelineChunkCount == 7);",
|
|
"static_assert(kMGPipePipelineChunkCount == 8); // G7 NEGATIVE CONTROL"),
|
|
("static_assert(kMGPipeDynamicChunkCount == 8);",
|
|
"static_assert(kMGPipeDynamicChunkCount == 9); // G7 NEGATIVE CONTROL"),
|
|
('static_assert(kMGPipePipelineChunkBytes == 396, "the pipeline subset is 396 bytes");',
|
|
'static_assert(kMGPipePipelineChunkBytes == 364, "G7 NEGATIVE CONTROL: 396 - 32 for ColorMasks");'),
|
|
('static_assert(kMGPipeDynamicChunkBytes == 772, "the dynamic subset is 772 bytes");',
|
|
'static_assert(kMGPipeDynamicChunkBytes == 804, "G7 NEGATIVE CONTROL: 772 + 32 for ColorMasks");'),
|
|
])
|
|
|
|
patch(source_path, [
|
|
(""" MGPipeRenderStateChunkAt(GlobalPipelineChunk(6)),
|
|
};""",
|
|
""" MGPipeRenderStateChunkAt(GlobalPipelineChunk(6)),
|
|
MGPipeRenderStateChunkAt(GlobalPipelineChunk(7)), // G7 NEGATIVE CONTROL
|
|
};"""),
|
|
(""" MGPipeRenderStateChunkAt(GlobalDynamicChunk(6)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(7)),
|
|
};""",
|
|
""" MGPipeRenderStateChunkAt(GlobalDynamicChunk(6)), MGPipeRenderStateChunkAt(GlobalDynamicChunk(7)),
|
|
MGPipeRenderStateChunkAt(GlobalDynamicChunk(8)), // G7 NEGATIVE CONTROL
|
|
};"""),
|
|
])
|
|
print("[g7] patched the chunk table")
|
|
PY
|
|
|
|
# --- 3. it must still COMPILE ----------------------------------------------------------------
|
|
# A build break here would mean the control proved the static_asserts work, not that the test
|
|
# still checks anything.
|
|
say "rebuilding with the demoted member"
|
|
if ! cmake --build "$BUILD_DIR" -j "$(nproc)" > "$LOG_DIR/build-after.log" 2>&1; then
|
|
say "the patched table did not compile - the control cannot distinguish 'the test failed' from"
|
|
say "'nothing was built'. See $LOG_DIR/build-after.log"
|
|
grep -m10 -E 'error:' "$LOG_DIR/build-after.log" >&2
|
|
exit 2
|
|
fi
|
|
say "the patched table still compiles, so the partition is still complete"
|
|
|
|
if [ "$PATCH_ONLY" = 1 ]; then
|
|
# Restore AND rebuild before returning. Leaving the build directory holding a library built
|
|
# from the deliberately-broken table would be the nastiest thing this script could do: the
|
|
# sources would look clean, and the next `ctest` in that directory would be measuring the
|
|
# break.
|
|
restore
|
|
trap - EXIT
|
|
if ! cmake --build "$BUILD_DIR" -j "$(nproc)" > "$LOG_DIR/build-restored.log" 2>&1; then
|
|
say "the tree did NOT rebuild after the restore - see $LOG_DIR/build-restored.log"
|
|
exit 2
|
|
fi
|
|
say "--verify-patch-only: the patch applies, compiles and reverts, and $BUILD_DIR is rebuilt from"
|
|
say "the restored sources. This is the MECHANISM check; it does NOT report the control as passed."
|
|
exit 0
|
|
fi
|
|
|
|
# --- 4. the test must now be RED -------------------------------------------------------------
|
|
say "running $TEST_NAME against the broken table"
|
|
if ctest --test-dir "$BUILD_DIR" -R "$TEST_NAME" --no-tests=error --output-on-failure \
|
|
> "$LOG_DIR/ctest-after.log" 2>&1; then
|
|
say "NEGATIVE CONTROL DID NOT TRIP: $TEST_NAME is still green with ColorMasks demoted to the"
|
|
say "dynamic half. glColorMask bumps m_pipelineStateVersion and no longer moves the pipeline"
|
|
say "subset hash, so the G7 invariant is violated and the test did not notice. The test is not"
|
|
say "checking what it claims to check."
|
|
cp -f "$LOG_DIR/ctest-after.log" ./g7-negative-control-failure.log
|
|
say "ctest output kept at ./g7-negative-control-failure.log"
|
|
exit 1
|
|
fi
|
|
|
|
# A red is not yet a pass. The control's claim is "demoting ColorMasks makes the setter-consistency
|
|
# test fail AND the failure names glColorMask's setter"; a SetterConsistency that had started
|
|
# failing for an unrelated reason would satisfy the first half and none of the second, and the
|
|
# caller (the integrator's D.3 reads this script's rc) would record it as "the negative control
|
|
# passed". So the answer is remembered here and decided at the end - AFTER the restore, because
|
|
# leaving a build directory holding the broken table is worse than any exit status.
|
|
TRIPPED_FOR_THE_RIGHT_REASON=1
|
|
if grep -q 'SetColorMask' "$LOG_DIR/ctest-after.log"; then
|
|
say "negative control tripped, naming SetColorMask"
|
|
else
|
|
TRIPPED_FOR_THE_RIGHT_REASON=0
|
|
say "negative control tripped, but its output does not name SetColorMask - the test failed for"
|
|
say "some other reason, so this is NOT a pass. Restoring first, then reporting it."
|
|
grep -m20 -E 'Failure|error|Expected|Actual' "$LOG_DIR/ctest-after.log" >&2
|
|
fi
|
|
|
|
# --- 5. put it back, and prove it went back --------------------------------------------------
|
|
restore
|
|
trap - EXIT
|
|
say "restored; rebuilding"
|
|
if ! cmake --build "$BUILD_DIR" -j "$(nproc)" > "$LOG_DIR/build-restored.log" 2>&1; then
|
|
say "the tree did NOT rebuild after the restore - see $LOG_DIR/build-restored.log"
|
|
exit 2
|
|
fi
|
|
if ! ctest --test-dir "$BUILD_DIR" -R "$TEST_NAME" --no-tests=error \
|
|
> "$LOG_DIR/ctest-restored.log" 2>&1; then
|
|
say "the tree did NOT go back to green after the restore - see $LOG_DIR/ctest-restored.log"
|
|
exit 2
|
|
fi
|
|
|
|
if [ "$TRIPPED_FOR_THE_RIGHT_REASON" = 0 ]; then
|
|
cp -f "$LOG_DIR/ctest-after.log" ./g7-negative-control-wrong-reason.log
|
|
say "INCONCLUSIVE: $TEST_NAME went red under the demotion but its output never names"
|
|
say "SetColorMask, so the red cannot be attributed to the demoted member. The tree is restored"
|
|
say "and green again; the failing output is kept at ./g7-negative-control-wrong-reason.log."
|
|
exit 1
|
|
fi
|
|
|
|
say "negative control tripped and the tree is green again"
|
|
exit 0
|