sys/vita: struct _reent in native ELF TLS, thread-exit reclaim, __cxa_thread_atexit - #115
Open
frangarcj wants to merge 8 commits into
Open
sys/vita: struct _reent in native ELF TLS, thread-exit reclaim, __cxa_thread_atexit#115frangarcj wants to merge 8 commits into
frangarcj wants to merge 8 commits into
Conversation
Newlib's own reentrancy now lives in native TLS via --enable-newlib-reent-thread-local, so drop __DYNAMIC_REENT__ and GETREENT_PROVIDED along with the kernel-TLS-slot reent pool and __getreent()/__getreent_for_thread(). The 0x89 kernel TLS slot stays, holding only the tls_data_ext and pthread_data_ext pointers pthread-embedded still needs: pte_osThreadCreate writes into a new thread's slot through sceKernelGetThreadTLSAddr(thid, ...) before that thread starts, and another thread's TPIDRURO-relative block is unreachable from outside it. _exit_thread_common now reclaims the thread's own TLS-resident buffers with _reclaim_reent(NULL) before its TLS block disappears, and no longer holds _newlib_reent_mutex across exit_func, which normally never returns and would leave the mutex owned by a dead thread. Accesses to ptr->_errno become _REENT_ERRNO(ptr), which is where the field lives once reent is thread-local. Assisted-by: Claude Opus 5
<sys/param.h> reaches <sys/config.h> without going through <_ansi.h>, so _WANT_REENT_THREAD_LOCAL is not defined when config.h looks for it, and the include guard blocks a second look. <reent.h> then falls back to _impure_ptr, which a thread-local build never defines: hash.o was left with an unresolved _impure_ptr, breaking any link that pulls in dbm_open and friends. Assisted-by: Claude Opus 5
With struct _reent in native TLS, the 256-entry table hosts nothing: it is a directory of pointers to memory the thread library allocates itself, and the kernel TLS slot it indexes is a kernel service the thread library can hold directly. __VITA_NEWLIB_THREAD_SLOT__ in the new <sys/vita_thread.h> selects who owns it. At 1, the default, everything behaves as before. At 0, the table, the mutex, the slot and vitasdk_get_tls_data/vitasdk_get_pthread_data/ vitasdk_delete_thread_reent are gone, and so is the 256-thread ceiling that ended in __builtin_trap. The value lives in the installed header rather than in a build flag because newlib and everything built against it must agree on it, and only the header guarantees that. Consumers that predate the header default to 1. vita_exit_thread and vita_exit_delete_thread stay in both, and are now declared: reclaiming a thread's own TLS-resident buffers before it exits is the one piece of this only newlib can do. Assisted-by: Claude Opus 5
__VITA_NEWLIB_THREAD_SLOT__ read as "there is a slot", which is true either way -- the slot exists in both, what changes is who opens it. Assisted-by: Claude Opus 5
Under --enable-newlib-reent-thread-local, _REENT_MP_FREELIST/_RESULT/ _P5S and _REENT_CVTBUF/_REENT_CLEANUP resolve to the calling thread's own _tls_* globals regardless of the ptr argument, so a second call after the first frees the same pointers again. The vita port is about to call this twice on some threads (once explicitly on the thread's own exit path, once from a process-wide EXIT event handler that also catches threads that never call it), so leaving the freed pointers dangling would double-free. NULL them out right after freeing, under _REENT_THREAD_LOCAL only: the non-thread-local path already guards reentry via ptr != _impure_ptr and isn't part of this. Assisted-by: Claude Sonnet 5
With struct _reent in native TLS, a thread's own newlib state (mprec bigints, _cvtbuf) is only reclaimed today if it exits through vita_exit_thread/vita_exit_delete_thread, which call _reclaim_reent(NULL) in _exit_thread_common. A raw sceKernelCreateThread thread that just returns from its entry function, or is exited/deleted from outside, never goes through either, so it leaks that state forever. The old kernel-TLS-slot reent pool didn't have this problem: it reclaimed on slot reuse regardless of how a thread ended. Register a process-wide SCE_KERNEL_THREAD_EVENT_TYPE_EXIT handler once at newlib init, mirroring how SceLibc registers its own TLS destructor handler (same 0x10027 process-wide mask, same guarantee that it runs in the exiting thread's own context). The handler just calls _reclaim_reent(NULL), which is now idempotent, so threads that already reclaim explicitly via vita_exit_thread get it twice, harmlessly. Registration failure is silent and non-fatal: without it, a thread just leaks the way it always did before this handler existed. Not unregistered in _free_vita_reent: its only caller is _exit(), which calls sceKernelExitProcess() right after, tearing down every kernel object the process owns. Assisted-by: Claude Sonnet 5
_reclaim_reent ends by running _REENT_CLEANUP, which is _cleanup_r. With the reent in native TLS the FILE list (__sglue) is global while that hook is per thread, so _cleanup_r called from an exiting thread closes every stream in the process and frees their buffers under the threads still using them. The hook is only ever set by __sinit, as a "stdio is initialised" mark for the calling thread; closing streams belongs to exit(). Clear it before reclaiming, on both the explicit path and the thread-exit handler. Assisted-by: Claude Fable 5
libstdc++ has no thread-exit hook on newlib targets, so its libsupc++ fallback keeps thread_local destructors in a pthread key and only runs them when the prebuilt library sees gthreads as active, which on a static link needs libpthread whole-archive; raw kernel threads never get them. Newlib on Vita does know when a thread exits now, so it can own this: a per-thread list in TLS, run from the thread-exit path before the reent is reclaimed, on every exit path including raw threads, and from atexit() for the thread that ends the process. libstdc++ only defers to libc when HAVE___CXA_THREAD_ATEXIT is defined at its configure time, which the --with-newlib branch never checks; that define is the toolchain side of this change. Assisted-by: Claude Fable 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this change?
struct _reentmoves into native ELF TLS (--enable-newlib-reent-thread-local); the kernel-TLS-slot reent pool,__getreent()and__DYNAMIC_REENT__go. What is left of the kernel slot (the two pointers the thread library needs) sits behind__VITA_NEWLIB_THREAD_SLOT__in the new<sys/vita_thread.h>, default 1 (as before). A thread's TLS-resident newlib state is reclaimed on every exit path: explicitly throughvita_exit_thread/vita_exit_delete_thread, and from a process-wideSCE_KERNEL_THREAD_EVENT_TYPE_EXIThandler registered at init (the same call SceLibc makes), with_reclaim_reentmade idempotent under thread-local reent and kept away from the stdio cleanup hook, which closes every stream in the process. newlib provides__cxa_thread_atexit, running C++thread_localdestructors from that same exit path. Also fixed:<sys/param.h>reaching<sys/config.h>before<newlib.h>, which lefthash.owith an unresolved_impure_ptrin the thread-local build.Why is this change needed?
Phase B of vitasdk/.github#3. With
--disable-tls,__threadcompiles to emulated TLS that degrades to a single process-global slot inside a thread library; newlib carried a 256-entry table with a hard ceiling ending in__builtin_trap(); the hardware needs neither. Details, measurements and the review discussion are in the RFC.Testing
newlib-tests, 88 tests, 82 pass, 0 failures, 6 skips. TLS group: initial values and isolation in raw threads and pthreads, multi-TU relocation,errnoisolation, concurrent%fformatting, thread churn with heap bounds on every exit path (raw return,sceKernelExitDeleteThread,vita_exit_threaddouble reclaim, pthread return), C++thread_localconstruction once per thread and destruction on pthread and raw-thread exit, a destructor callingsnprintf/strtodover 84 threads with a flat heap.sceKernelExitProcessin console mode and its file log lags by a 4 KiB buffer, so the end marker sometimes never reached the file before the runner's timeout. The log flush is part of the same Vita3K PR.Compatibility
Breaks the target ABI; every package is rebuilt at the series cut, per the RFC. Removed:
__getreent,__errno,_impure_ptr, the fields ofstruct _reent;vitasdk_get_tls_data,vitasdk_get_pthread_data,vitasdk_delete_thread_reentremain while the switch is 1 and go with it. Ordinaryerrnoand__threadcode is source-compatible. Requires the toolchain built with--enable-tlsand thevita-elf-createof vitasdk/vita-toolchain#294; libstdc++ must be configured withHAVE___CXA_THREAD_ATEXITfor the C++ part (vitasdk/buildscripts#185). RFC: vitasdk/.github#3.Third-party material
None.
AI assistance
Additional context
RFC: vitasdk/.github#3. Companions: vitasdk/pthread-embedded#24, vitasdk/vita-toolchain#294, vitasdk/buildscripts#185. Moving the switch needs a clean rebuild of newlib and pthread-embedded on an incremental tree; the buildscripts PR makes it part of both build identities.