D464 - Spawned guest threads get their own thread-local storage
measured - 2026-09-02 (user-directed /loop: overnight oracle-free crunch)
Only the main thread had thread-local storage.
install_main_thread_tls builds it a block, installs its
fs base and registers it with the context-switch backstop
(D433) - but a thread the guest spawned got none of that:
orbistoun-kernel's spawn body reserved a stack and entered
guest code, and the new thread's first fs:-relative access
read a zero base and faulted. PPSA04263 died this way at
image+0x2ba47bc (mov rax, fs:[0] on a thread
whose stack was 0x6100…), and the fault reporter's
register-base guess could not name it, because the access is
segment-relative and naming it would mean disassembling the guest
(worklog 286).
The fix is the call-budget inversion (D238 shape). The kernel spawns
threads but cannot build a TLS block - it holds no template and does not
depend on the loader - so a fn() hook
(thread::install_thread_start) is called at the top of
every new guest thread, on that thread, before any guest code. The
worker installs it. The effectful half of the main-thread setup was
extracted into build_tls_block(base, layout, tdata) -
reserve, render_block, install the fs base,
remember - and the main-thread path now also stores the
template (TlsLayout, tdata) so a spawned thread can build
its own block from it, at its own arena (THREAD_TLS_BASE,
0x6A00…, clear of the stacks, the main block and the
mapping arena). No template - a title with no thread-locals - and the
hook is never registered, so nothing changes for those.
Verified, two observations. PPSA04263 went from
10,123 calls to 332,914 (+322,791,
+34 distinct imports), faulting far later at
image+0x196b91a: a program cannot make three hundred
thousand more calls without the thread whose first instruction used to
fault now running. No regression - PPSA02664, PPSA03416, PPSA21564 and
PPSA25872 are unchanged. The block, base install and backstop
registration all run per-thread, so the Windows fs-reset
(D433) is handled for a spawned thread exactly as for the main one:
remember sets a thread-local, and the fault handler
restores that thread's own base.