6.0 KiB
_data_decode_memory legal-C codegen research
Result
The residual is closed by removing the fallback assignment
memory_size = count; after system_exit(-1) in the invalid-element-size
switch arm. With that one source change, XDK 3911 emits the January section
exactly:
- padded size: 304 / 304 bytes;
- relocations: 19 / 19, including identity and order;
- normalized SHA-256:
eb39377415e6c3e07f763e8f0f42f6ecafaf61acc0fab30fbc06062b9dc465d8; - normalized byte differences: zero;
- all 15 sibling functions remain strict exact.
This was independently reproduced from a generated copy of the complete translation unit under this research directory. Production source was not edited by this research lane; the one-line candidate was already present as another lane's uncommitted edit when the matrix ran.
Reproduction environment and command
- Compiler: XDK 3911
CL.Exe, banner version 13.00.9254.1 for x86. - Flags:
/nologo /c /O2 /Oy- /DDEBUG /Dxboxplus the same repository include roots as the generated campaign build. - Target:
build/split/source/memory/data_encoding.objfrom the January 14, 2002 executable. - Comparator: the current hardened
tools/coff_compare.pyimplementation, usingsection_infos_equalfor size, relocation count/identity, normalized bytes, and section ownership.
Run from the closure-wave worktree:
& "C:\Users\isabe\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe" `
research\data_decode_memory_codegen\run_matrix.py
The script creates only ignored disposable sources, objects, and
results.json below research/data_decode_memory_codegen. It compiles each
candidate as the complete translation unit and checks every one of the 16 code
owners, so a locally improved decoder cannot hide a sibling regression.
Why the source shape matters
The fallback assignment keeps memory_size semantically live on the default
edge. VC7 then allocates state, memory, and memory_size to the wrong
callee-saved register permutation for the January object. With the assignment
absent, the allocator coalesces the invalid-path value with the count
parameter and produces the January ESI/EDI/EBX allocation, including the
partial-BX switch and the post-system_exit reload:
call _system_exit
mov ebx, [ebp+0Ch]
add esp, 14h
That is a general codegen lesson: a statement on a nominally unreachable error edge can change live ranges and register allocation across an entire function even though normal inputs never execute it.
Original-bug note
system_exit is intended to terminate, but its current C declaration is an
ordinary returning void function. If it were ever to return for an invalid
element_size, the exact source leaves memory_size indeterminate and its
subsequent use is undefined behavior. The January machine code itself reloads
count on this path, but C does not guarantee that result.
The matching source should therefore carry a plain-English bug comment. The
safe, nonmatching fix is to restore memory_size = count; before break; (or
to repair the termination contract project-wide); restoring it currently
produces the old nonexact hash and must not be mistaken for an exact source.
Bounded experiment matrix
All variants used the complete TU and the campaign compiler/flags. Diffs
counts normalized bytes in _data_decode_memory; every row retained 19
relocations.
| ID | Source shape | Size | Diffs / first | Nonexact functions | Verdict |
|---|---|---|---|---|---|
| v00 | no default fallback assignment | 304 | 0 / none | 0 | strict exact |
| v01 | byte *memory |
304 | 0 / none | 0 | code-neutral exact spelling |
| v02 | char *memory |
304 | 0 / none | 0 | code-neutral exact spelling |
| v03 | buffer pointer born before switch | 288 | 163 / +0x5 | 4 | reject; regresses three siblings |
| v04 | current pointer born before switch | 304 | 211 / +0x8 | 4 | reject; regresses three siblings |
| v05 | offset born before switch | 304 | 209 / +0x8 | 3 | reject; regresses two siblings |
| v06 | split result base-load and offset-add | 304 | 0 / none | 0 | code-neutral exact spelling |
| v07 | failure-first final gate | 304 | 0 / none | 0 | code-neutral exact spelling |
| v08 | named final-gate boolean | 304 | 0 / none | 0 | code-neutral exact spelling |
| v09 | 32-bit count alias live across switch | 288 | 176 / +0x5 | 1 | reject |
| v10 | initialize memory_size = 0 |
288 | 114 / +0x8 | 1 | safe but nonexact |
| v11 | initialize memory_size = count |
320 | 279 / +0x8 | 1 | safe but nonexact |
| v12 | assign zero after system_exit |
304 | 94 / +0x5 | 1 | safe but nonexact |
| v13 | return NULL after system_exit |
304 | 166 / +0x8 | 1 | safe but nonexact |
| v14 | restore memory_size = count after system_exit |
304 | 114 / +0x4 | 1 | old baseline; nonexact |
The exact spellings v01/v02/v06/v07/v08 are not recommended changes: they add no evidence beyond showing that those local spellings are optimizer-neutral. The least invasive exact source is v00.
Do not repeat
In addition to v01-v14 above, the prior closeout log already exhausts E01-E17:
declaration order, split/redundant initializers, register hints on locals and
parameters, short aliases, multiplication/shift spellings, predecessor-TU
perturbations, const, and a state alias. It also rules out volatile,
pragmas, flag changes, inline assembly, aliasing violations, and byte forcing.
See docs/object_matching_logs/data_encoding_obj.md for the measured hashes
and sibling regressions.
Recommended admission gate
Rebuild the real TU, compare all 16 code owners plus all 14 January-owned
non-code sections with tools/coff_compare.py, run the whole-TU regression
gate and consolidated build, add the bug/fix comment, then mark the object
Matching only if all strict checks remain green. No fuzzy acceptance is
permitted: the candidate is genuinely byte- and relocation-exact. Because
objdiff represents the local jump-table labels differently, the progress
ledger still needs a fail-closed semantic bookkeeping entry that re-runs the
hardened comparison on every progress build.