|
| 1 | +# Pre-Port Audit Checklist (Layout-Pinned C++ Struct Ports) |
| 2 | + |
| 3 | +**Authored:** theologian, 2026-05-13 (per supervisor 00:51:18Z (B), pythia #368 audit-completeness gap) |
| 4 | + |
| 5 | +**Scope:** Mandatory checklist for any C++→C port commit that touches a C++ struct or class with C-side layout-pinned representation (HirInstrLayout, HirBeginInlinedFunction, HirFrameStateLayout, LirOperand, LirBasicBlock, LirFunction, LirInstruction, etc.). Supplements `synthetic_falsifier_at_gate.md` (1546c4330e) with a structural pre-commit audit specifically targeting the cascade-audit-3-category failure class (`feedback_cascade_audit_3category`). |
| 6 | + |
| 7 | +## Motivation |
| 8 | + |
| 9 | +c14 (a6500318e0, 2026-05-13 00:06:50Z) shipped after a "single-pass audit" that cited only category (a) — `sizeof(HirBeginInlinedFunction) == sizeof(BeginInlinedFunction)`. The audit missed: |
| 10 | + |
| 11 | +- (b) field-offset cross-validation for HirBeginInlinedFunction `func` and `caller_state_ptr` (only `fullname` was offsetof-validated) |
| 12 | +- (c) hardcoded literal-offset assumption: the port called `frame_asm_c_frame_header_size` (the asm-helper variant that adds `+sizeof(void*)` when `ENABLE_SHADOW_FRAMES` undef) instead of the LIR-generation-equivalent `jit_frame_header_size` direct call. The asm-helper variant produces +8-byte offsets at runtime, crashing JIT in `test_phoenix_benchmark_correctness::test_fibonacci`. |
| 13 | + |
| 14 | +The audit metric measured velocity (~20s dispatch→auth) and missed completeness. This checklist makes completeness auditable structurally, not by author judgment. |
| 15 | + |
| 16 | +## The 3 Categories (per `feedback_cascade_audit_3category`) |
| 17 | + |
| 18 | +Every port commit touching layout-pinned C++ structs MUST cite all three categories explicitly in the audit (chat or commit body), with specific C-side equivalents named: |
| 19 | + |
| 20 | +### (a) Sizeof equivalence |
| 21 | + |
| 22 | +For each C++ struct/class touched by the port (including via `reinterpret_cast`, field access, or method dispatch): |
| 23 | + |
| 24 | +- Locate the `static_assert(sizeof(HirX) == sizeof(CppY))` line in the appropriate verifier file (e.g., `hir_instr_c_verify.cpp`, `lir_instr_c_verify.cpp`). |
| 25 | +- Cite the file:line. |
| 26 | +- If MISSING, ADD the static_assert as part of the port commit OR a precursor commit (do NOT proceed without one — the c14 SIGSEGV class). |
| 27 | + |
| 28 | +### (b) Offsetof equivalence per field |
| 29 | + |
| 30 | +For each C++ field accessed by the port via `instr->field` or equivalent C-side cast: |
| 31 | + |
| 32 | +- Locate the `static_assert(offsetof(HirX, c_field) == offsetof(CppY, cpp_field_))` line in the verifier file. |
| 33 | +- Cite the file:line per field. |
| 34 | +- If MISSING for any accessed field, ADD the offsetof asserts BEFORE the port commit (precursor) OR in the same commit as part of the verifier-extension scope. |
| 35 | +- NOTE: sizeof equivalence (a) is NECESSARY but NOT SUFFICIENT; an empty base class subobject (e.g., `InlineBase`) can shift derived-class field offsets without changing total sizeof if the shift is symmetric on both sides — an offsetof per-field check catches asymmetric layouts. |
| 36 | + |
| 37 | +### (c) Hardcoded literal-offset equivalents |
| 38 | + |
| 39 | +For each C-side function or constant the port calls/uses as a substitute for a C++ method or constant: |
| 40 | + |
| 41 | +- Verify the SUBSTITUTE has the SAME semantic behavior as the C++ original. Common asymmetries: |
| 42 | + - asm-helper variants vs LIR-generation variants (c14's `frame_asm_c_frame_header_size` vs `jit_frame_header_size` direct call) |
| 43 | + - Constants with version-conditional adjustments (`ENABLE_SHADOW_FRAMES`, `PY_VERSION_HEX`) |
| 44 | + - C-side fields that look identical-named but encode different shifts (e.g., `caller_state_ptr` vs `caller_state_` — same field, but the question is what the *value* means; if C++ wraps it via accessor with extra logic, the raw field read may differ) |
| 45 | +- If the substitute's semantics differ in any way, EITHER: |
| 46 | + - Construct the C++-equivalent expression manually in the C port (matching the args the C++ wrapper passes — c14's amend used `jit_frame_header_size(code, lightweight, sizeof(void*), sizeof(PyObject*))` to match `frameHeaderSize`'s exact behavior) |
| 47 | + - Add a static_assert proving the substitute produces the same value for the relevant inputs (e.g., for c14: `static_assert(JIT_FRAME_HEADER_SIZE_AS_USED_IN_C_HELPER == sizeof(jit::FrameHeader))`) |
| 48 | + - Document the asymmetry in the port comment AND the commit body, with a falsifier test if runtime behavior differs. |
| 49 | + |
| 50 | +## Pre-Audit Procedure |
| 51 | + |
| 52 | +Before posting the c-series scope-confirmation request to chat: |
| 53 | + |
| 54 | +1. **Enumerate (a):** list every C++ struct/class the port touches; for each, file:line of the sizeof static_assert. If any are missing, decide whether to (i) add the assert as part of port commit OR (ii) add as precursor commit. Document the decision in the audit post. |
| 55 | + |
| 56 | +2. **Enumerate (b):** list every C++ field the port accesses (directly or via accessor); for each, file:line of the offsetof static_assert. If missing, same decision tree as (a). Specifically: when the C++ class uses MULTIPLE INHERITANCE (even from an empty base), per-field offsetof asserts are NECESSARY because empty-base-optimization is implementation-defined. |
| 57 | + |
| 58 | +3. **Enumerate (c):** list every C-side function and constant the port uses as a substitute for C++. For each substitute, locate the C-side definition (file:line) AND the C++ original (file:line); compare them line-by-line for asymmetries (extra args, version conditionals, extra arithmetic). If any asymmetry, document the resolution. |
| 59 | + |
| 60 | +4. **Compose audit post:** include all three category enumerations explicitly. Failure to cite all three categories is a `feedback_cascade_audit_3category` violation (medic-flaggable). |
| 61 | + |
| 62 | +## Pre-Audit Procedure Example (c14 retrospective) |
| 63 | + |
| 64 | +What c14 audit SHOULD have said: |
| 65 | + |
| 66 | +> (a) Sizeof equivalence: |
| 67 | +> - HirBeginInlinedFunction: sizeof matched at hir_instr_c_verify.cpp:281 ✓ |
| 68 | +> - HirFrameStateLayout: sizeof matched at hir_instr_c_verify.cpp:106 ✓ |
| 69 | +> |
| 70 | +> (b) Offsetof equivalence per field: |
| 71 | +> - HirBeginInlinedFunction.func — UNVERIFIED ⚠ (only .fullname offset asserted at line 268). Multiple inheritance from InlineBase (empty class) may cause subtle layout shift. ACTION: ADD offsetof(HirBeginInlinedFunction, func) == offsetof(BeginInlinedFunction, func_) static_assert as part of c14 commit. |
| 72 | +> - HirBeginInlinedFunction.caller_state_ptr — UNVERIFIED ⚠. Same. ACTION: ADD. |
| 73 | +> - HirFrameStateLayout.parent — VERIFIED at hir_instr_c_verify.cpp:120 ✓ |
| 74 | +> - HirFrameStateLayout.code — VERIFIED at hir_instr_c_verify.cpp:113 ✓ |
| 75 | +> |
| 76 | +> (c) Hardcoded literal-offset equivalents: |
| 77 | +> - C++ `frameHeaderSize(PyCodeObject*)` (frame_header.h:67) calls `jit_frame_header_size(code, lightweight, sizeof(FrameHeader), sizeof(PyObject*))` with sizeof(FrameHeader)=sizeof(void*) on 3.12+. |
| 78 | +> - C-side candidate `frame_asm_c_frame_header_size(PyCodeObject*)` (frame_asm_c.c:351) calls jit_frame_header_size with hardcoded JIT_FRAME_HEADER_SIZE=sizeof(void*) BUT THEN ADDS `+sizeof(void*)` when ENABLE_SHADOW_FRAMES is undef. ⚠ ASYMMETRIC. ACTION: do NOT use frame_asm_c_frame_header_size; call jit_frame_header_size directly with C++-equivalent args. |
| 79 | +
|
| 80 | +If c14 audit had cited (b) and (c) at this level of detail, the SIGSEGV would have been caught at audit time, not at gate. |
| 81 | + |
| 82 | +## Enforcement |
| 83 | + |
| 84 | +- **Author-side:** every port commit's audit post (chat or commit body) MUST include all three category enumerations. |
| 85 | +- **Gatekeeper:** APPROVE checklist for layout-pinned-port-class commits MUST verify all three category citations are present and concrete (file:line, not "verified"). |
| 86 | +- **Medic:** [MEDIC-WARNING] on any port commit landing without 3-category audit citation. |
| 87 | +- **Supervisor:** disposes flags from medic. |
| 88 | + |
| 89 | +## Out-of-scope |
| 90 | + |
| 91 | +- Routine code commits that don't touch layout-pinned C++ structs (no audit needed). |
| 92 | +- Documentation-only commits. |
| 93 | +- Test-only commits. |
| 94 | +- Refactors within a single language (e.g., C++ method extraction without C interop). |
| 95 | + |
| 96 | +## Non-coverage caveat |
| 97 | + |
| 98 | +This checklist catches structural layout asymmetry. It does NOT catch: |
| 99 | + |
| 100 | +- Algorithmic differences between C and C++ implementations (the C-port body could compute the wrong thing even if all layout is correct). |
| 101 | +- Side effects (e.g., reference counting, exception state) that C++ handles implicitly via destructors but C must handle explicitly. |
| 102 | +- Threading/synchronization assumptions. |
| 103 | + |
| 104 | +These remain the author's responsibility to verify via runtime tests (gate Tier 1, force_compile, ABBA). The 3-category audit is a NECESSARY but NOT SUFFICIENT condition. |
| 105 | + |
| 106 | +## Self-falsifying disclaimer |
| 107 | + |
| 108 | +If a future port commit cites the 3 categories at this level of detail and STILL produces a runtime bug at gate, this checklist is incomplete and needs extension. The c14 retrospective example above is the calibration anchor: any pre-port audit that would NOT have caught c14's frame_asm_c asymmetry given this checklist is a checklist failure, not an author failure. |
0 commit comments