Skip to content

Commit 9e342ab

Browse files
committed
phoenix-asm: phx_fs_ptr SIB-disp32 encoding fix (W-C2 pydebug)
Phoenix-introduced regression in the phoenix-asm x86_64 encoder. phx_fs_ptr (TLS access for PyThreadState) returned PhxMem with m.base default-initialized to {0} = RAX (id=0). encode_modrm_mem treated this as `[RAX + disp]` addressing and emitted 64 4c 8b 58 c8 mov %r11, %fs:-0x38(%rax) instead of the intended 64 4c 8b 1c 25 c8 ff ff ff mov %r11, %fs:[-0x38] Under release-mode RAX happened to be 0 at the load_tstate call site so fs:[disp+rax] resolved correctly. Under pydebug RAX was non-zero (testkeeper 16:46:13Z lldb register dump showed rax=0x4 at the crash) so fs:[rax-0x38] read the wrong TLS slot, returned NULL/garbage tstate, and crashed at the subsequent mov rax, [r11+0x38] (read tstate->cframe). Fix: phx_fs_ptr sets is_abs_addr=1 + abs_addr=sign-extend(offset) so encode_modrm_mem's existing absolute-address branch (x86_64.c:182-188) emits the SIB pure-disp32 form (ModR/M=04, SIB=25, disp32). The FS segment prefix is independently emitted by emit_segment_prefix; the byte sequence required for FS-segment + pure-displacement is identical to the absolute-address form. Why valgrind didn't catch (testkeeper 15:46:07Z Phoenix valgrind clean): valgrind tracked memory addresses correctly; the wrong tstate was logically wrong but didn't trip valgrind's instrumentation. The crash deferred to the later [r11+0x38] read. Why ARM64 unaffected (testkeeper 16:58:33Z 4/4 PASS without fix): AArch64 has no FS segment; load_tstate reads TLS via mrs(TPIDR_EL0) + ldr — different code path entirely. Verification (testkeeper 16:52:24Z + 16:58:33Z): x86_64 pydebug + fix: simple-add / closure / generator / exception 4/4 PASS (was SIGSEGV pre-fix on 3 of 4) ARM64 pydebug (no fix needed): 4/4 PASS Dual-arch confirms (β) hypothesis: bug is x86_64-only Structural debt: is_abs_addr is reused here for the SIB-disp32 shape, NOT for its conventional 'absolute 64-bit address' semantics. DSecondary-2 (theologian 16:49:17Z, supervisor 16:55:58Z) tracks the proper refactor: add explicit has_base flag to PhxMem; refactor phx_fs_ptr to use it instead of overloading is_abs_addr. Scheduled immediately after this push to pay structural debt while surface is fresh. Auth chain: testkeeper root cause 16:46:13Z (lldb full prologue disasm) + theologian patch-shape APPROVE 16:49:17Z (with DSecondary-2 logged) + testkeeper 4/4 fixture verify x86_64 16:52:24Z + ARM64 (β) confirm 16:58:33Z; supervisor authorization 16:55:58Z queue re-sequence. Closes audit Group C C2 W-C2 workstream. Closes pythia python#126 single-fixture concern (4-fixture pre-fix verification per supervisor 16:15:41Z).
1 parent cc081f1 commit 9e342ab

1 file changed

Lines changed: 17 additions & 1 deletion

File tree

Python/jit/phoenix_asm/x86_64.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,28 @@ static inline PhxMem phx_dword_ptr(PhxGp base, int32_t offset) {
216216
return m;
217217
}
218218

219-
/* FS segment memory operand — for TLS access (e.g. PyThreadState) */
219+
/* FS segment memory operand — for TLS access (e.g. PyThreadState).
220+
*
221+
* D-1777048937 W-C2 fix: pre-fix this returned PhxMem with m.base
222+
* default-initialized to RAX (id=0), so encode_modrm_mem treated the
223+
* operand as `[RAX + disp]` and emitted `mov r, fs:disp(%rax)`. Under
224+
* release mode RAX happened to be 0 at the load_tstate call site, so
225+
* fs:[disp+0] resolved correctly; under pydebug RAX was non-zero and
226+
* fs:[disp+rax] read the wrong TLS slot, returning NULL/garbage tstate
227+
* and crashing later on tstate->cframe deref. is_abs_addr=1 triggers
228+
* the SIB pure-disp32 encoding (encode_modrm_mem:182-188) which emits
229+
* `04 25 disp32` — exactly the encoding FS-segment + pure-displacement
230+
* addressing requires. is_abs_addr is reused here for the SIB-disp32
231+
* shape, NOT for its conventional 'absolute 64-bit address' semantics.
232+
* TODO (DSecondary-2): add explicit has_base flag to PhxMem; refactor
233+
* phx_fs_ptr to use it instead of overloading is_abs_addr. */
220234
static inline PhxMem phx_fs_ptr(int32_t offset) {
221235
PhxMem m = {0};
222236
m.offset = offset;
223237
m.size = 8;
224238
m.segment = 4; /* FS segment override */
239+
m.is_abs_addr = 1; /* triggers SIB pure-disp32 encoding (no base) */
240+
m.abs_addr = (uint64_t)(int64_t)offset; /* sign-extend for encoder */
225241
return m;
226242
}
227243

0 commit comments

Comments
 (0)