Skip to content

Commit ebec018

Browse files
committed
W-I3-RUNTIME-ASSERT (IV)+(III): BasicBlock::id immutability gate + sentinel
Per docs/w-i3-runtime-assert-spec.md (theologian 21:57:02Z, supervisor 22:10:02Z, librarian 22:05:19Z framework guidance). Closes pythia python#128 / python#131 python#1 named risk: I3 invariant (BasicBlock::id is allocation- monotonic AND never mutated post-allocation) is grep-only, not runtime-asserted. Phase B B-gamma's PhxBcBlockArray (Tier 8 SECOND-PILOT, push 40 4145fe3) indexes by BasicBlock::id. A future HIR pass that renumbers ids (SSA-destruction-style cache-locality renumbering, peephole block-splitter, speculative-inlining clone) would silently corrupt the dense-array lookup; I2 read-site check catches out-of- range, NOT stale-mapping-correct-range. (IV) CI grep gate — scripts/check_i3_invariant.sh + Step 1h wire: - Detects "->id =", ".id =", set_id(/setId( patterns in Python/jit/hir/. - Allow-list excludes legitimate non-BasicBlock matches: * HirLoadAttrSpecial::id (void* attribute identifier, different class) * next_register_id_, cache_id_ (different fields, trailing underscore) * test_*.c, hir_instr_c_verify.cpp (read-path testing, per W44 ALLOW_LIST precedent) - Falsification-tested: synthetic patch adding "block->id = 42;" to Python/jit/hir/synth_i3_violation.cpp triggers GATE FAIL EXIT=1 under --strict; removing the patch returns to GATE PASS EXIT=0. - Wired as Step 1h in scripts/gate_phoenix.sh per librarian 22:05:19Z framework guidance, mirroring Steps 1e/1g delegation shape (capture OUTPUT, tee to RESULTS_FILE, GATE_PASS=0 + FAILURES on nonzero). (III) JIT_DCHECK pydebug sentinel — PhxBcBlockEntry.sentinel_id: - Adds int sentinel_id field under #ifdef Py_DEBUG to PhxBcBlockEntry (zero release-build cost; pydebug entry size 8 -> 12 bytes). - Insert sets sentinel_id = block_id at insert time. - Lookup verifies sentinel_id == block_id via JIT_DCHECK_C; mismatch fires JIT_CHECK_C abort with diagnostic indicating either a post-allocation id mutation OR inserter/reader index disagreement. - catches the (IV) miss-class: id mutation via member function / indirect pointer write that grep doesn't pattern-match. Combined cost: zero release runtime; ~45min implementation. (I) sentinel-always and (II) generation-counter REJECTED per spec §3 as reverse perf trade without strong evidence the always-on detection is required vs (IV)+(III) catching at commit + pydebug. Build implications: Py_DEBUG-conditional layout change to PhxBcBlockEntry. Per JIT Header Change Protocol (CLAUDE.md), requires full distclean rebuild; testkeeper to verify both release (sentinel field absent, struct == 8 bytes) and pydebug (sentinel field present, struct == 12 bytes, JIT_DCHECK_C does not fire on normal workload). ARM64 verification via standard ARM64 retroactive-debt path once devgpu004 SSH-2FA infra restored. Auth: theologian spec 21:57:02Z + supervisor CONCUR 21:57:20Z + 22:10:02Z; librarian framework guidance 22:05:19Z (Step 1h shape mirroring 1e/1g, scripts/check_i3_invariant.sh --strict).
1 parent 85ed449 commit ebec018

3 files changed

Lines changed: 192 additions & 1 deletion

File tree

Python/jit/hir/builder_state_c.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include <stdlib.h>
2929
#include <string.h>
3030

31+
#include "cinderx/Common/jit_log_c.h" /* JIT_DCHECK_C (W-I3 (III) sentinel) */
32+
3133
#ifdef __cplusplus
3234
extern "C" {
3335
#endif
@@ -234,6 +236,14 @@ static inline void *phx_block_map_lookup(const PhxBlockMap *m, int key) {
234236
typedef struct PhxBcBlockEntry {
235237
int start; /* BCIndex.value() */
236238
int end; /* BCIndex.value() */
239+
#ifdef Py_DEBUG
240+
/* W-I3-RUNTIME-ASSERT (III) per docs/w-i3-runtime-assert-spec.md §2:
241+
* pydebug-only sentinel storing the block_id this entry was inserted
242+
* for. Verified at lookup; mismatch => I3 invariant violation
243+
* (BasicBlock::id mutated post-allocation, OR inserter/reader
244+
* disagree on indexing). Zero release-build cost. */
245+
int sentinel_id;
246+
#endif
237247
} PhxBcBlockEntry;
238248

239249
typedef struct PhxBcBlockArray {
@@ -287,16 +297,28 @@ static inline void phx_bc_block_array_insert(
287297
}
288298
a->data[block_id].start = start;
289299
a->data[block_id].end = end;
300+
#ifdef Py_DEBUG
301+
a->data[block_id].sentinel_id = block_id; /* W-I3 (III) */
302+
#endif
290303
if (needed > a->count) {
291304
a->count = needed;
292305
}
293306
}
294307

295308
/* Read array[block_id]. Caller must guarantee block_id < a->count
296309
* (invariant I2: never look up an id beyond createBlocks high-water).
297-
* Returns by value; entry is just 8 bytes. */
310+
* Returns by value; entry is just 8 bytes (release) / 12 bytes (pydebug
311+
* with W-I3 (III) sentinel field). */
298312
static inline PhxBcBlockEntry phx_bc_block_array_at(
299313
const PhxBcBlockArray *a, int block_id) {
314+
#ifdef Py_DEBUG
315+
JIT_DCHECK_C(
316+
a->data[block_id].sentinel_id == block_id,
317+
"W-I3 invariant violation: bc_block_array[%d].sentinel_id=%d "
318+
"(BasicBlock::id mutated post-allocation, OR inserter/reader "
319+
"disagree on indexing)",
320+
block_id, a->data[block_id].sentinel_id);
321+
#endif
300322
return a->data[block_id];
301323
}
302324

scripts/check_i3_invariant.sh

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
#!/bin/bash
2+
# check_i3_invariant.sh — W-I3-RUNTIME-ASSERT (IV) gate. Detects
3+
# id-mutation patterns on BasicBlock-class objects that would violate
4+
# Tier 8 SECOND-PILOT Phase B's I3 invariant: BasicBlock::id is
5+
# allocation-monotonic AND never mutated post-allocation.
6+
#
7+
# I3 underpins PhxBcBlockArray's dense-array O(1) lookup at
8+
# Python/jit/hir/builder_state_c.h:298 (phx_bc_block_array_at). Any
9+
# future HIR pass that renumbers ids (SSA-destruction-style cache
10+
# locality renumbering, peephole block-splitter, speculative-inlining
11+
# clone) would silently corrupt the lookup.
12+
#
13+
# Per W-I3-RUNTIME-ASSERT spec docs/w-i3-runtime-assert-spec.md §2 (IV)
14+
# + §7 (theologian 21:57:02Z + librarian 22:05:19Z framework guidance):
15+
# this script is the (IV) layer. (III) JIT_DCHECK pydebug sentinel
16+
# lives in builder_state_c.h's PhxBcBlockEntry.
17+
#
18+
# Usage:
19+
# scripts/check_i3_invariant.sh # default scope (warn-only)
20+
# scripts/check_i3_invariant.sh --files # show file:line per match
21+
# scripts/check_i3_invariant.sh --strict # exit 1 on any violation
22+
#
23+
# Behavior:
24+
# - Greps Python/jit/hir/ for id-mutation patterns:
25+
# <var>->id = ... (pointer-style)
26+
# <var>.id = ... (value-style)
27+
# set_id(/setId( (setter methods)
28+
# - Filters via ALLOW_LIST to exclude:
29+
# - HirLoadAttrSpecial::id (void* attribute-id field, not BasicBlock)
30+
# - Test/verify files (read-path testing per W44 precedent)
31+
# - The BasicBlock ctor itself (initializer-list, not mutation)
32+
# - Reports any remaining matches as VIOLATIONS.
33+
#
34+
# Exit codes:
35+
# 0 — no violations (gate clean)
36+
# 1 — at least 1 violation (--strict) OR script error
37+
38+
set -euo pipefail
39+
40+
cd "$(dirname "$0")/.."
41+
REPO_ROOT="$(pwd)"
42+
43+
STRICT=0
44+
SHOW_FILES=0
45+
for arg in "$@"; do
46+
case "$arg" in
47+
--strict) STRICT=1 ;;
48+
--files) SHOW_FILES=1 ;;
49+
*) echo "ERROR: unknown arg '$arg'" >&2
50+
echo "Usage: $0 [--strict] [--files]" >&2
51+
exit 1 ;;
52+
esac
53+
done
54+
55+
SEARCH_PATHS="Python/jit/hir"
56+
57+
# Allow-list patterns (file or file:line) for known-OK matches.
58+
# Each entry should be commented with WHY it is legitimate.
59+
#
60+
# - hir_c_api.cpp 'l->id = id' on HirLoadAttrSpecial: the .id field is
61+
# a void* attribute identifier on HirLoadAttrSpecial, NOT a
62+
# BasicBlock id. Different class, different field semantics.
63+
# - hir.h 'next_register_id_' / 'cache_id_': not BasicBlock id; trailing
64+
# underscore disambiguates. Pattern uses \bid\b so these won't match.
65+
# - hir.h 'BasicBlock(int id_) : id(id_)': initializer-list assignment
66+
# inside the ctor, NOT a post-allocation mutation. Pattern excludes
67+
# ': id(' constructor-init form.
68+
# - test_phx_block_map.c, hir_instr_c_verify.cpp: tests/verify files
69+
# exercising read-path. Per W44 ALLOW_LIST precedent.
70+
ALLOW_LIST_FILES_REGEX='/(test_.*\.c|.*_test\.c|hir_instr_c_verify\.cpp)$'
71+
72+
# Allow-list specific symbols whose ".id" or "->id" mutation is
73+
# unrelated to BasicBlock::id. Add new entries with justification.
74+
# Each entry is a regex matching the FULL grep line.
75+
ALLOW_LIST_LINES_REGEX='HirLoadAttrSpecial|l->id\s*=\s*id;|next_register_id_|cache_id_'
76+
77+
echo "=== check_i3_invariant.sh — W-I3-RUNTIME-ASSERT (IV) gate ==="
78+
echo "HEAD: $(git rev-parse HEAD)"
79+
echo "Search scope: $SEARCH_PATHS"
80+
echo "Allow-list files: $ALLOW_LIST_FILES_REGEX"
81+
echo "Allow-list lines: $ALLOW_LIST_LINES_REGEX"
82+
echo ""
83+
84+
# Patterns to detect (all id-mutation forms targeting a presumed-BasicBlock):
85+
# ->id = ... (single = followed by non-= to exclude == comparisons)
86+
# .id = ... (same; but more permissive — many false positives, hence
87+
# ALLOW_LIST filtering)
88+
# set_id( (setter-method form)
89+
# setId( (camelCase setter)
90+
PATTERNS_PTR='->\s*id\s*=\s*[^=]'
91+
PATTERNS_VAL='\.\s*id\s*=\s*[^=]'
92+
PATTERNS_SET='\b(set_id|setId)\s*\('
93+
94+
echo "=== Step 1: enumerate id-mutation candidates in $SEARCH_PATHS ==="
95+
ALL_TMP=$(mktemp)
96+
trap "rm -f $ALL_TMP" EXIT
97+
98+
set +e
99+
{
100+
grep -rnE -e "$PATTERNS_PTR" --include='*.c' --include='*.cpp' --include='*.h' $SEARCH_PATHS 2>/dev/null
101+
grep -rnE -e "$PATTERNS_VAL" --include='*.c' --include='*.cpp' --include='*.h' $SEARCH_PATHS 2>/dev/null
102+
grep -rnE -e "$PATTERNS_SET" --include='*.c' --include='*.cpp' --include='*.h' $SEARCH_PATHS 2>/dev/null
103+
} | sort -u > "$ALL_TMP"
104+
set -e
105+
106+
N_RAW=$(wc -l < "$ALL_TMP")
107+
echo "Raw matches: $N_RAW"
108+
109+
# Filter via allow-lists.
110+
echo ""
111+
echo "=== Step 2: filter via allow-lists ==="
112+
FILTERED_TMP=$(mktemp)
113+
trap "rm -f $ALL_TMP $FILTERED_TMP" EXIT
114+
115+
set +e
116+
grep -vE "$ALLOW_LIST_FILES_REGEX" "$ALL_TMP" | grep -vE "$ALLOW_LIST_LINES_REGEX" > "$FILTERED_TMP"
117+
set -e
118+
119+
N_VIOLATIONS=$(wc -l < "$FILTERED_TMP")
120+
echo "After allow-list filtering: $N_VIOLATIONS violation(s)"
121+
echo ""
122+
123+
if [ "$N_VIOLATIONS" -eq 0 ]; then
124+
echo "=== Verdict ==="
125+
echo "GATE PASS: $N_RAW raw matches all in allow-list; zero I3-violation candidates."
126+
exit 0
127+
fi
128+
129+
echo "=== Violations ==="
130+
if [ "$SHOW_FILES" -eq 1 ]; then
131+
cat "$FILTERED_TMP" | sed 's/^/ /'
132+
else
133+
cat "$FILTERED_TMP" | cut -d: -f1-2 | sort -u | sed 's/^/ /'
134+
fi
135+
136+
echo ""
137+
echo "=== Verdict ==="
138+
echo "GATE FAIL: $N_VIOLATIONS candidate I3 violation(s)."
139+
echo ""
140+
echo "I3 invariant: BasicBlock::id is allocation-monotonic AND never"
141+
echo "mutated post-allocation. PhxBcBlockArray's dense-array O(1)"
142+
echo "lookup depends on this. Each violation must be either:"
143+
echo " - Fixed (remove the id mutation)"
144+
echo " - Re-architected (drop PhxBcBlockArray's id-indexed shape; see"
145+
echo " docs/w-i3-runtime-assert-spec.md (II) generation-counter as"
146+
echo " fallback)"
147+
echo " - Allow-listed (if NOT actually a BasicBlock::id mutation; add"
148+
echo " to ALLOW_LIST_LINES_REGEX or ALLOW_LIST_FILES_REGEX with a"
149+
echo " one-line justification comment)"
150+
[ "$STRICT" -eq 1 ] && exit 1
151+
exit 0

scripts/gate_phoenix.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,24 @@ else
219219
echo "W45 §3.5 derivation-drift falsifier: PASS" | tee -a "$RESULTS_FILE"
220220
fi
221221

222+
# Step 1h: W-I3-RUNTIME-ASSERT (IV) gate — BasicBlock::id immutability.
223+
# Detects id-mutation patterns that would silently corrupt PhxBcBlockArray
224+
# dense-array O(1) lookup (Tier 8 SECOND-PILOT Phase B). Per
225+
# docs/w-i3-runtime-assert-spec.md §2 (IV) + §7. Wired per librarian
226+
# 22:05:19Z framework guidance (mirrors 1e/1g delegation shape).
227+
echo "" | tee -a "$RESULTS_FILE"
228+
echo "--- Step 1h: W-I3 Invariant Gate ---" | tee -a "$RESULTS_FILE"
229+
I3_OUTPUT=$("$SCRIPT_DIR/check_i3_invariant.sh" --strict 2>&1) && I3_EXIT=0 || I3_EXIT=$?
230+
echo "$I3_OUTPUT" | tee -a "$RESULTS_FILE"
231+
if [ "$I3_EXIT" -ne 0 ]; then
232+
echo "GATE FAIL — W-I3 invariant gate detected BasicBlock::id mutation" | tee -a "$RESULTS_FILE"
233+
GATE_PASS=0
234+
I3_VIOLATIONS=$(echo "$I3_OUTPUT" | grep -c "candidate I3 violation" || true)
235+
FAILURES="$FAILURES w_i3:$I3_VIOLATIONS"
236+
else
237+
echo "W-I3 invariant gate: PASS" | tee -a "$RESULTS_FILE"
238+
fi
239+
222240
# Step 2: Verify JIT compiles and executes
223241
echo "" | tee -a "$RESULTS_FILE"
224242
echo "--- Step 2: JIT Smoke Test ---" | tee -a "$RESULTS_FILE"

0 commit comments

Comments
 (0)