Native-targeted TLS allocation and release — 2026-09-22

The native Win98 fixture exposed five runtime differences: 64 rather than 80 slots, no reusable indices, no cross-thread clearing, successful double-free, and incorrect LastError on Set/Free/exhaustion. This change implements the measured API sequence.

Ownership and synchronization

The existing 64-byte TLS_NEXT_INDEX_SHARED region now contains a high-water mark at +0, a lock at +4, the registered-vector list head at +8 and three bitmap words at +12/+16/+20. No region size or address changes. TLS_SLOT_COUNT is the single production capacity constant; vector size is derived from it.

Allocation claims an unused bit, clearing its slot in registered vectors before publication. Release rejects an unset bit, clears all registered vectors, then makes the bit reusable. The lock also protects registration and API value accesses. No allocation or host call occurs while holding it. Clearing writes the vector itself: direct guest FS:[0x2c] reads see the reset, not only calls through TlsGetValue. Application-owned pointed-to memory is never released by TlsFree.

High-water metadata is retained for existing spawn protocols, but is not an allocation cursor: stale snapshots cannot resurrect freed holes. Raising the minimum reserves only the previously unpublished suffix. The loader's static PE TLS allocation uses the same reservation helper and retains its template and zero-fill behavior. Main and both Worker/cooperative setup paths now ask WAT for a zeroed, registered 80-slot vector instead of allocating private 256-byte vectors in three places. Allocation failure is surfaced rather than publishing a NULL TLS vector.

Verification

A compiler-only negative control replacing tls_clear_index with an empty body fails the raw main-vector assertion immediately after Free (the old value 0x11223344 remains). It changes neither source nor a shipped artifact. The memory-map check passes with 211 regions and no layout change. Generated API/metadata, fragment balance, logical operands, handler ESP, silent-stub, test-tier and whitespace gates also pass; the generated-wrapper inventory is 238 with 22 manual adapters remaining. Worker metadata-refresh coverage passes.

Limits / remaining work

Per-thread TLS vector storage was already retained until process disposal; this change retains that lifetime and adds an eight-byte registry node per instance. Thread-exit reclamation is not implemented here. The new registry must be unlinked under the TLS lock before anyone introduces vector freeing. Hard termination while holding a process lock remains a broader Worker lifecycle concern; this test does not force termination inside the critical section. Racing guest writes made directly to TLS memory are not intercepted.

This is not full static-TLS/DLL thread-attach conformance: the test verifies the existing PE-template load path, not per-DLL template cloning into new threads. The allocation-failure branches are defensive and not fault-injected here. No browser game sweep, performance benchmark or full clean release is claimed; tests run against the shared worktree.