Skip to content

feat(libkernel): initialize created guest thread MXCSR to 0x9fc0 at entry boundary - #7565

Open
pushkarreddyy wants to merge 3 commits into
boykopovar:mainfrom
pushkarreddyy:feat/libkernel-guest-thread-mxcsr
Open

pushkarreddyy wants to merge 3 commits into
boykopovar:mainfrom
pushkarreddyy:feat/libkernel-guest-thread-mxcsr

Conversation

@pushkarreddyy

@pushkarreddyy pushkarreddyy commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What

Measured on PS5 firmware 12.70: threads of a PS5 payload process start with MXCSR 0x9fc0 (Flush-to-Zero and Denormals-are-Zero enabled, all FP exceptions masked).

In Aps5CallGuestEntry_nid_no_patch, created guest threads now set 0x9fc0 upon entering guest execution. The 16-byte MXCSR scratch slot is allocated below %rbp (sub $16, %rsp / add $32, %rsp), preserving the sentinel frame-pointer zero record for guest_pthread_frame_chain. In RunThread and scePthreadExit, host worker MXCSR is recorded and restored only after finishThread has completed, ensuring that guest thread destructors and TSD key destructors execute symmetrically under guest MXCSR 0x9fc0 across both explicit exit and normal return paths, while host native workers return to host MXCSR upon thread completion. Note: the main ELF thread entry stub is handled separately in #7594 and is not covered here.

Tested

Linux x86_64 and Windows: core/libs/tests/PthreadStart.cpp exercises 10,000 guest thread starts across normal return and scePthreadExit explicit exits. Tests verify that guest execution and cleanup (WorkerThreadDtor) observe 0x9fc0, the native worker thread (WorkerTlsHook TLS destructor) restores host MXCSR upon guest completion, and the process main thread preserves its host MXCSR throughout; python tools/check_conventions.py --base origin/main passes with 0 findings.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: Uncharted: Legacy of Thieves Collection (PPSA05684) feat(libkernel): initialize created guest thread MXCSR to 0x9fc0 at entry boundary #7565
  • GPUs tested: n/a
  • AI-assisted: yes

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One test coverage gap: hostMxcsrBefore and hostMxcsrAfter are sampled on the test process main thread, while scePthreadCreate runs Aps5CallGuestEntry_nid_no_patch on a separate native worker. That assertion would remain unchanged even if the wrapper failed to restore the worker’s MXCSR after a returning guest callback. The explicit restore instruction is present in the diff, but the PR’s stated host-preservation test does not exercise it. Could you add a same-worker post-callback check if practical, or narrow the Tested claim to what the current test observes?

@github-actions github-actions Bot added libkernel docs fix error Closes an issue (Fixes #N) labels Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 3986 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 4563 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 4630 docs/dev/debt/libkernel.md
PR 5021 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 6740 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
docs/dev/debt/libkernel.md
PR 6936 docs/dev/debt/libkernel.md
PR 7088 docs/dev/debt/libkernel.md
PR 7105 docs/dev/debt/libkernel.md
PR 7121 docs/dev/debt/libkernel.md
PR 7135 docs/dev/debt/libkernel.md
PR 7149 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7192 docs/dev/debt/libkernel.md
PR 7447 docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7804 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 8592 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 9156 docs/dev/debt/libkernel.md
PR 9199 docs/dev/debt/libkernel.md
PR 9293 docs/dev/debt/libkernel.md
PR 9297 docs/dev/debt/libkernel.md
PR 9302 docs/dev/debt/libkernel.md
PR 9389 docs/dev/debt/libkernel.md
PR 9557 docs/dev/debt/libkernel.md
PR 9566 docs/dev/debt/libkernel.md
PR 9843 docs/dev/debt/libkernel.md
PR 9938 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 10025 docs/dev/debt/libkernel.md
PR 10319 docs/dev/debt/libkernel.md
PR 10442 docs/dev/debt/libkernel.md
PR 11896 docs/dev/debt/libkernel.md
PR 11939 docs/dev/debt/libkernel.md
PR 12104 docs/dev/debt/libkernel.md
PR 12130 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 12544 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 12560 docs/dev/debt/libkernel.md
PR 12642 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 12682 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12688 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12700 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12965 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12970 docs/dev/debt/libkernel.md
PR 13560 docs/dev/debt/libkernel.md
PR 14103 docs/dev/debt/libkernel.md
PR 14128 docs/dev/debt/libkernel.md
PR 14212 docs/dev/debt/libkernel.md
PR 14264 docs/dev/debt/libkernel.md
PR 14310 docs/dev/debt/libkernel.md
PR 14832 docs/dev/debt/libkernel.md
PR 14854 docs/dev/debt/libkernel.md
PR 14886 docs/dev/debt/libkernel.md
PR 14894 docs/dev/debt/libkernel.md

@ionut27001

Copy link
Copy Markdown
Contributor

Two corrections about what #7548 covers, since this PR cites it:

@MarcZX10

Copy link
Copy Markdown
Contributor

The new #7548 report follow-up confirms a scope gap beyond my earlier test comment: the console main thread also reads MXCSR 0x9fc0, while the AnyPS5 main guest entry bypasses Aps5CallGuestEntry_nid_no_patch and the change in this PR. The issue report also says this main-thread measurement is part of the same payload probe. As written, the PR changes spawned guest threads only and does not fully fix #7548. Could you either cover the main guest-entry boundary too (without leaving host-side work under guest MXCSR), with a main-entry regression, or narrow the PR title/body/Fixes: #7548 claim to created threads and coordinate the remaining main-thread behavior separately? I have not run the Windows test locally.

@jotitadev

Copy link
Copy Markdown
Contributor

Ran this on macOS (x86-64 under Rosetta 2), head 296d1d44a. The Linux and Windows CI jobs were skipped on this PR, so this is the first full-suite run I know of.

Machine: Apple M2 Max, macOS 27.2, Apple clang 21, full x86-64 build. Base: main e5f18ea with @mugurc's macOS series (ci/macos-series-2 on his fork) and the open macOS fixes it uses, since main alone does not link the libraries on macOS. Non-GPU suite with and without this PR.

guest_pthread_frame_chain fails with this PR, every run:

guest pthread frame chain: the last frame of a guest thread's chain also has a saved frame pointer of 0

It passes on the same tree without the PR. Aps5CallGuestEntry_nid_no_patch pushes two zeros and points %rbp at them. That pair is the zero record that ends the guest thread's frame chain. The new stmxcsr (%rsp) / movl $0x9fc0, 4(%rsp) write into the saved frame pointer half of that record. After that it holds 0x00009fc0_00001f80, not 0. This does not depend on the host, so Linux and Windows should fail the same way.

Keeping the MXCSR values in their own slot below the record fixes it:

     mov %rsp, %rbp
+    sub $16, %rsp
+    .cfi_adjust_cfa_offset 16
     stmxcsr (%rsp)
     movl $0x9fc0, 4(%rsp)
     ldmxcsr 4(%rsp)
     mov %rdi, %rax
     mov %rsi, %rdi
     call *%rax
     ldmxcsr (%rsp)
-    add $16, %rsp
-    .cfi_adjust_cfa_offset -16
+    add $32, %rsp
+    .cfi_adjust_cfa_offset -32

With that change, guest_pthread_frame_chain (still 3 frames), guest_pthread_start, guest_raise_exception and the other 23 guest_pthread* tests pass.

Other results:

  • guest_pthread_start passes with the PR. With only the test applied it fails with Guest thread MXCSR does not have FTZ and DAZ bits set, so the thread-side check covers the change under Rosetta too.

  • I removed the restoring ldmxcsr (%rsp) and guest_pthread_start still passed. That confirms @MarcZX10's point: the host-preservation check samples the test's main thread, so it cannot see a missing restore.

  • Guest TSD destructors do not run under 0x9fc0 when the entry returns. finishThread runs the key destructors and the thread-exit callback after Aps5CallGuestEntry_nid_no_patch has restored the host MXCSR. A local probe with a scePthreadKeyCreate destructor that reads MXCSR printed:

    Guest thread ends by MXCSR in the entry MXCSR in the key destructor
    returning from the entry 0x9fc0 0x1f80
    scePthreadExit 0x9fc0 0x9fc0

    So the same guest destructor runs with different FTZ/DAZ depending on how the thread ended.

  • On macOS with @mugurc's feat/libkernel-macos-main-thread branch (not opened yet), the guest main thread is also a second host thread: Aps5StartGuest_nid_no_patch runs the entry from RunGuest on a new pthread_create thread, which starts at 0x1f80. Covering the main thread there means setting MXCSR in RunGuest, not only in the relinker's entry stub.

  • Apart from guest_pthread_frame_chain, the non-GPU suite fails the same tests with and without the PR. Those failures are macOS gaps that the series does not cover yet. guest_refused_connect failed in about half of the runs on both trees.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@pushkarreddyy pushkarreddyy changed the title feat(libkernel): initialize guest thread MXCSR to 0x9fc0 at guest entry boundary feat(libkernel): initialize created guest thread MXCSR to 0x9fc0 at entry boundary Oct 18, 2026
@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

Pushed commit 251ef0f28 addressing all feedback:

  1. Stack Unwinding & Frame Chain Record Isolation (@jotitadev):

    • In Aps5CallGuestEntry_nid_no_patch, allocating 16 bytes below %rbp (sub $16, %rsp / .cfi_adjust_cfa_offset 16 and .cfi_adjust_cfa_offset -32 / add $32, %rsp) isolates the MXCSR storage slot into [rbp - 16].
    • This prevents clobbering the two zero words pushed at (%rbp), preserving the sentinel frame pointer record for guest_pthread_frame_chain.
  2. Same-Worker Host MXCSR Restoration Test Coverage (@MarcZX10, @jotitadev):

    • Registered a thread destructor hook via sceKernelSetThreadDtors(WorkerThreadDtor) in core/libs/tests/PthreadStart.cpp.
    • When a guest thread finishes by returning to Aps5CallGuestEntry_nid_no_patch, the wrapper restores the host MXCSR via ldmxcsr (%rsp). Subsequently, finishThread executes WorkerThreadDtor on the exact same worker thread.
    • The test asserts workerMxcsrAfterReturn.load() == hostMxcsrBefore for non-explicit returns (and confirms 0x9fc0 remains active during explicit exit cleanup), directly verifying the worker's restore instruction.
  3. Narrowed PR Scope & Title/Debt Update (@ionut27001, @MarcZX10):

    • Updated the PR title to feat(libkernel): initialize created guest thread MXCSR to 0x9fc0 at entry boundary and set Fixes: none.
    • Clarified in ### What and docs/dev/debt/libkernel.md that this PR specifically covers created guest threads under firmware 12.70 payload findings. The main ELF thread entry stub (which bypasses Aps5CallGuestEntry_nid_no_patch) and macOS RunGuest entry remain open and will not be closed by this PR.

@MarcZX10

Copy link
Copy Markdown
Contributor

One remaining state-consistency issue from the new destructor probe: when the guest entry returns normally, Aps5CallGuestEntry_nid_no_patch restores the host MXCSR before RunThread calls finishThread, so threadDtors and RunKeyDtors execute under host MXCSR. When the guest calls scePthreadExit, finishThread runs before the assembly wrapper returns, so the same cleanup executes under guest MXCSR 0x9fc0. A guest TSD destructor therefore observes different floating-point state depending on whether the entry returns or exits explicitly. Could the host MXCSR restoration be delayed until guest-thread cleanup has completed on the normal-return path, and could the regression assert the destructor sees 0x9fc0 in both cases while the worker itself is restored afterward? The current 10,000-iteration test checks the opposite state for normal return cleanup.

@MarcZX10

Copy link
Copy Markdown
Contributor

Update on the main-entry gap I noted earlier: I opened #7594 for the Linux and Windows relinker startup stubs. It sets guest MXCSR to 0x9fc0 at main ELF entry and restores the host MXCSR if that entry returns. This is separate from the created-thread trampoline covered here; macOS’s distinct launcher path remains outside #7594.

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-ran on macOS (x86-64 under Rosetta 2), head 251ef0f28. Linux and Windows CI are still skipped on this PR.

Same setup as my earlier run: Apple M2 Max, macOS 27.2, Apple clang 21, full x86-64 build. Base: main d3d8d79 with @mugurc's macOS series and the open macOS fixes it uses.

  • guest_pthread_frame_chain now passes (5/5), so the zero record is intact.
  • guest_pthread_start passes (5/5). The new destructor check covers the restore. With only the ldmxcsr (%rsp) after the call removed, it fails every run with Worker thread host MXCSR was not restored upon guest return. With only the test applied on the base it still fails with Guest thread MXCSR does not have FTZ and DAZ bits set.
  • Non-GPU suite: same failures with and without the PR, all of them macOS gaps that the series does not cover yet. The 51 pthread/thread/raise_exception tests pass on both.

This does not address @MarcZX10's last point: on a normal return the key destructors still run under the host MXCSR. The new check asserts that state.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

Pushed commit 1f99bd900 addressing the destructor state-consistency:

  1. Retain 0x9fc0 Across Normal-Return Cleanup:

    • In Aps5CallGuestEntry_nid_no_patch, removed the immediate ldmxcsr upon returning from call *%rax.
    • In RunThread, hostMxcsr is saved prior to Aps5CallGuestEntry_nid_no_patch and restored via ldmxcsr immediately after finishThread(self, retval) returns.
    • Consequently, both threadDtors and ThreadLifecycle::RunKeyDtors() execute under guest MXCSR 0x9fc0 whether the thread terminates via normal return or explicit exit (scePthreadExit).
  2. Unified Regression Assertion in PthreadStart.cpp:

    • Updated the destructor hook assertion to confirm (workerMxcsrInDtor.load() & 0x9fc0) == 0x9fc0 across all 10,000 iterations (both explicit exit and normal return paths).
    • Confirmed main thread host MXCSR preservation upon join across the entire run.
  3. Coordination with fix(relinker): initialize main guest MXCSR at entry #7594:

@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

@jotitadev Please note repository review etiquette: formal GitHub Reviews (Submit Review / Review Decisions) are reserved exclusively for AnyPS5 repository maintainers with merge permissions. If you have local reproduction data or technical observations to share, post them strictly as standard PR discussion comments rather than submitting formal reviews.

Regarding the technical point: that state-consistency issue was already addressed in commit 1f99bd900 (pushed right before your review).

Specifically:

  • In Aps5CallGuestEntry_nid_no_patch, the immediate restore instruction upon guest return was removed, keeping guest MXCSR 0x9fc0 active across thread exit.
  • In RunThread, hostMxcsr is recorded prior to entry and restored via ldmxcsr only after finishThread(self, retval) returns.
  • Consequently, both threadDtors and ThreadLifecycle::RunKeyDtors() execute under 0x9fc0 consistently regardless of whether the thread exits via scePthreadExit or normal return.
  • In PthreadStart.cpp, the destructor hook now asserts that (workerMxcsrInDtor.load() & 0x9fc0) == 0x9fc0 across all 10,000 iterations for both paths.

@github-actions github-actions Bot removed the fix error Closes an issue (Fixes #N) label Oct 18, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

The stubs look right. One question before merge: with the main thread (here) and created guest threads (#7565) at 0x9fc0, host code called from the guest, and host threads created from a guest thread on Linux (which inherit MXCSR), will run with FTZ/DAZ. Which host work runs in that context, especially the shader recompiler's constant folding and the driver workers? Should host-owned worker threads set the host default MXCSR when they start, so console FP semantics stay on guest code?

@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

@oneandonlydean Thanks for the question! Here is an analysis of what host work executes in that context and how host threads are affected:

  1. Host Work Called Directly From Guest Threads:

    • PRX runtime exports & AGC driver: PRX runtime calls (libkernel, libc, libSceAgcDriver, libSceAudioOut2, etc.) and AGC draw/state dispatch run synchronously on the guest thread's stack with the current MXCSR (0x9fc0).
    • Shader recompiler constant folding: ConstantFolder (core/shader/recompiler/Optimization/src/ConstantFolder.cpp) does not perform IEEE floating-point arithmetic folding (FAdd, FMul, FDiv, etc.); it exclusively folds integer math, bitwise operations, bitcasts (BitCastU32F32, BitCastF32U32), and conditional SelectF32 arms. The SRT evaluator (SrtEvaluator.cpp / SrtTape.cpp) does evaluate table arithmetic (ConvertF32U32, FPMul32, FPTrunc32), but this evaluates game-provided shader resource tables where console FTZ/DAZ semantics are actually the desired behavior matching retail hardware.
  2. Host-Owned Worker Threads (Linux clone Inheritance):

    • On Linux, host worker threads spawned from a guest thread (e.g., AGC Submission queue workers, PipelineLibrary / PipelineCache background compilers, and ShaderDiskCache writer) inherit the parent's 0x9fc0 via clone().
    • While most driver tasks involve queue submission, synchronization, SPIR-V translation, and host I/O, host libraries or underlying Vulkan driver internal helper threads could theoretically be sensitive to denormal flushing.
  3. Recommendation:

    • Yes, having host-owned worker threads explicitly initialize the host default MXCSR (0x1f80) at their thread entry is the cleanest architectural boundary. It guarantees that console FP semantics (0x9fc0) remain strictly bound to guest code (and synchronous guest PRX/AGC invocations), while all asynchronous host background workers (AGC submission, shader disk cache, Vulkan compilation workers) run in standard host IEEE-754 mode. We can add _mm_setcsr(0x1f80) at host worker thread entry points in a small follow-up.

Also, whenever you have a moment, could you please take a look at two open PRs that are cleanly rebased on latest origin/main with all pre-flight and CI checks passing green?

Thanks!

@MarcZX10

Copy link
Copy Markdown
Contributor

The host-worker boundary you asked about is now covered in follow-up #7622. Its helper resets MXCSR to 0x1f80 at native std::thread entry across runtime, AGC, shader cache, AvPlayer and libkernel workers, and at the shader-registration pthread trampoline. The focused Linux regression verifies worker MXCSR 0x1f80 while the caller remains at 0x9fc0. This addresses worker inheritance; it does not claim to isolate synchronous host work that runs on a guest thread, which remains a separate boundary question.

@MarcZX10 MarcZX10 mentioned this pull request Oct 18, 2026
12 tasks done
@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

@MarcZX10 @oneandonlydean Thanks for putting together follow-up #7622!

As noted in #7622's description and test suite, the host-worker MXCSR isolation specifically builds upon the guest caller environment remaining at 0x9fc0. PR #7565 establishes this primary prerequisite—initializing created guest threads to 0x9fc0 at the Aps5CallGuestEntry_nid_no_patch boundary, preserving the sentinel frame-chain record for unwinding, and ensuring guest TSD key destructors in finishThread execute symmetrically under 0x9fc0 across both explicit exit and normal return paths.

Because #7565 provides the foundational guest execution state that #7622's tests verify caller inheritance against, having #7565 take precedence and merge first establishes the core guest baseline cleanly, allowing #7622 to land smoothly on top to handle host background worker defaults.

@MarcZX10

Copy link
Copy Markdown
Contributor

At the current head (1f99bd9), the restore instruction is present after finishThread, but the committed host-preservation assertion still samples MXCSR only on the test process main thread. The worker dtor check runs before RunThread restores host MXCSR, so it verifies guest cleanup state rather than the worker state after guest return. The implementation looks straightforward; please either add a same-worker observation if practical or narrow the Tested claim to avoid saying this test verifies host preservation.

@jotitadev

Copy link
Copy Markdown
Contributor

Linux run of 1f99bd900, since the Linux and Windows CI jobs were skipped on this PR and the existing runs are Windows and macOS.

Machine: Linux x86_64, i7-8700, Ubuntu 26.04, GCC 15.2, Release, BUILD_TESTING=ON, full build.
Head 1f99bd900 merged onto main 84c1a4d9a (clean merge).

  • Builds. The 26 pthread tests (ctest -R pthread) pass, including guest_pthread_start and guest_pthread_frame_chain. Non-GPU suite (ctest -E "^agc|gpu|vulkan|video_out"): 739/739, same as main.

  • Without the ldmxcsr (%rsp) in Aps5CallGuestEntry_nid_no_patch, guest_pthread_start fails with Guest thread MXCSR does not have FTZ and DAZ bits set, so the guest-side check works.

  • Without the ldmxcsr %0 restore after finishThread in RunThread, guest_pthread_start still passes 3/3. This is @MarcZX10's point: the host check samples the test's main thread, not the worker.

  • On Linux the restore only runs when the entry returns. scePthreadExit calls finishThread and then longjmps to StartNativeThread, which skips the rest of RunThread, the restore included. I added a thread_local object with a destructor that reads MXCSR to guest_pthread_start (set in CheckThread, 10000 threads, 3 runs):

    exit path MXCSR in the thread_local destructor
    entry returns (6666 threads) host value, all 6666
    scePthreadExit (3334 threads) 0x9fc0, all 3334

    So after an explicit exit (or a cancel, which goes through scePthreadExit), ThreadGuard, ReleaseThread and the native thread's TLS destructors run with FTZ and DAZ set. Restoring in StartNativeThread after the setjmp block would cover both paths. The Windows _endthreadex path looks the same from the source, but I did not run it.

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 19, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from 1f99bd9 to 8659e82 Compare October 19, 2026 05:40
@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

Pushed commit 8659e828b rebased onto latest origin/main (4ffb0cd12) addressing both points:

  1. Host MXCSR Restoration Across Explicit Exits (scePthreadExit) and Normal Returns (@jotitadev, @MarcZX10):

    • Stored the native caller's hostMxcsr inside PthreadPrivate.
    • In RunThread, finishThread executes all guest TSD destructors under 0x9fc0, followed immediately by asm volatile("ldmxcsr %0" : : "m"(hostMxcsr)).
    • In scePthreadExit, finishThread executes all guest cleanup handlers and key destructors under 0x9fc0, restores host MXCSR via ldmxcsr prior to thread exit/jump, and on Linux the setjmp receiver in StartNativeThread additionally executes ldmxcsr before guard and thread termination.
    • Consequently, both explicit exit (scePthreadExit / cancel) and normal return cleanup run under 0x9fc0, while host worker thread teardown runs under host MXCSR.
  2. Same-Worker MXCSR Observation Across 10,000 Iterations (@MarcZX10):

    • Added a thread_local hook (WorkerTlsHook) in core/libs/tests/PthreadStart.cpp that is activated on each spawned worker. When the native worker thread terminates, its C++ TLS destructor executes on that exact same native thread and reads the thread's final MXCSR.
    • After scePthreadJoin, main() asserts workerMxcsrAfterReturn.load() == hostMxcsrBefore while simultaneously checking (workerMxcsrInDtor.load() & 0x9fc0) == 0x9fc0. This directly observes host MXCSR restoration on the same worker across all 10,000 iterations for both normal return (6,666 iterations) and scePthreadExit (3,334 iterations) paths.

@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch 2 times, most recently from f685cc3 to bf99760 Compare November 1, 2026 15:42
@github-actions github-actions Bot added the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 1, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch 2 times, most recently from b20fd5f to 24b3017 Compare November 2, 2026 12:00
@github-actions github-actions Bot added review queue Next in the maintainer review queue: checks pass, no open change request, no conflict and removed review queue Next in the maintainer review queue: checks pass, no open change request, no conflict labels Nov 2, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from 24b3017 to ac92c61 Compare November 2, 2026 16:52
@github-actions github-actions Bot added review queue Next in the maintainer review queue: checks pass, no open change request, no conflict and removed review queue Next in the maintainer review queue: checks pass, no open change request, no conflict labels Nov 2, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from ac92c61 to 7826de0 Compare November 3, 2026 15:41
@github-actions github-actions Bot added review queue Next in the maintainer review queue: checks pass, no open change request, no conflict and removed review queue Next in the maintainer review queue: checks pass, no open change request, no conflict labels Nov 3, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from 7826de0 to 84b8fca Compare November 4, 2026 13:35
@github-actions github-actions Bot removed the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 4, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from 84b8fca to e0a574c Compare November 4, 2026 17:46
@github-actions github-actions Bot added the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 4, 2026
@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from e0a574c to c91c5f9 Compare November 5, 2026 12:38
@github-actions github-actions Bot added review queue Next in the maintainer review queue: checks pass, no open change request, no conflict and removed review queue Next in the maintainer review queue: checks pass, no open change request, no conflict labels Nov 5, 2026

@Superseabas Superseabas left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Windows run of the current head, c91c5f98f4, since the earlier reviews didn't build or run the Windows paths (_beginthreadex, _endthreadex in scePthreadExit, adoption in scePthreadSelf). They work here, and the test catches the loss of each restore that matters. One non-blocking suggestion for the adoption capture, which the test can't see on Windows.

Head merged onto main 492ed5c5fe (clean merge, local only). Windows 11 Home 10.0.26200, Ryzen 7 7700X, WinLibs GCC 16.2.0 (x86_64-ucrt-posix-seh, r3), Release, BUILD_TESTING=ON.

Tests. The 343 tests matching XairRrR's set (pthread|thread_self|thread_atexit|thread_callback|kernel_sys_thread|libc_threads|raise_exception|cancel|thread_local|tls|exception) pass 3 of 3 on main and on this PR. guest_pthread_start and guest_pthread_frame_chain pass 5 of 5. The non-GPU suite (ctest -E '^agc|gpu|vulkan|video_out' -j 8, 2,886 tests) gives 2,883 on both trees. The same three fail on main here: windows_crash_thread_name (#15578), plus test_registration and select_tests_MissingCommandTests, which need the Windows symlink privilege.

Red checks on guest_pthread_start, each a local edit of the merged tree, 3 runs:

Edit Result
none passes
Thread.cpp from main (the PR's header kept) fails: Guest thread MXCSR does not have FTZ and DAZ bits set
the restore in scePthreadExit removed fails: Worker thread host MXCSR was not restored upon guest return
the restore in RunThread removed passes
the restore in the Windows StartNativeThread removed passes
both of those removed fails: the same message
the capture in the Windows StartNativeThread removed passes
the capture in the Windows scePthreadSelf adoption removed passes

On Windows, as on Linux, the restores in RunThread and StartNativeThread cover each other on a normal return; only the one in scePthreadExit runs before _endthreadex.

XairRrR's second point, on Windows. I set workerMxcsrInDtor and workerMxcsrAfterReturn to 0 before each scePthreadCreate and before nativeWorker starts. The test still passes 3 of 3, and it still fails without the scePthreadExit restore or without both normal-return restores. So each check, the adopted-thread case included, reads its own thread's value here, and the TLS hook runs on threads that end through _endthreadex. With a print added to the guest-MXCSR creator case, the main thread reads 0x1f80, and a worker created while its creator held 0x9fc0 reads 0x1f80 coming in and 0x1f80 after it returns. Windows threads don't inherit the creator's MXCSR.

Non-blocking. On Windows the two captures can't be told apart from the 0x1f80 default, since every thread starts there, so removing either one passes. For StartNativeThread that's harmless. For adoption, a native thread can hold any MXCSR before calling scePthreadSelf, and setting one in the test covers the capture:

    std::thread nativeWorker([&] {
        workerTlsHook.active = true;
        const std::uint32_t adoptedMxcsr = 0x1fc0;
        asm volatile("ldmxcsr %0" : : "m"(adoptedMxcsr));
        Pthread adoptedSelf = scePthreadSelf();

with the check after nativeWorker.join() comparing against 0x1fc0 instead of hostMxcsrBefore. I tried it locally: it passes 3 of 3 on this head and fails 3 of 3 (Adopted thread host MXCSR was corrupted upon scePthreadExit) with the Windows adoption capture removed. Nothing in it is Windows-specific, so it should cover the POSIX adoption capture too, which I didn't run.

check_conventions.py --base main passes. No title run, and #7594 and #7622 aren't on this tree.

AI-assisted: yes (Claude Code)

@pushkarreddyy
pushkarreddyy force-pushed the feat/libkernel-guest-thread-mxcsr branch from c91c5f9 to db410ee Compare November 6, 2026 17:53
@pushkarreddyy

Copy link
Copy Markdown
Contributor Author

Thank you @Superseabas for testing the Windows execution paths and for the adoption coverage suggestion!

I have rebased cleanly onto current origin/main and incorporated your suggestion in commit db410ee8f6 (core/libs/tests/PthreadStart.cpp): the native worker now loads custom 0x1fc0 prior to scePthreadSelf(), and the test verifies that workerMxcsrAfterReturn preserves 0x1fc0 across scePthreadExit(). This validates that thread adoption captures and restores arbitrary incoming host MXCSR values across both Windows and POSIX. Conventions pass cleanly.

@github-actions github-actions Bot removed the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 6, 2026

@Superseabas Superseabas left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-checked the new head, db410ee8f6, on Windows. Thanks for adding the adoption case. git range-diff against c91c5f98f4, which I approved, shows your two earlier commits unchanged; the new commit only adds the 0x1fc0 adoption check in PthreadStart.cpp. It passes here and now catches the missing adoption capture on Windows.

Head merged onto main 9f2b939606 (clean merge, local only). Windows 11 Home 10.0.26200, Ryzen 7 7700X, WinLibs GCC 16.2.0 (x86_64-ucrt-posix-seh, r3), Release.

  • The 344 tests matching pthread|thread_self|thread_atexit|thread_callback|kernel_sys_thread|libc_threads|raise_exception|cancel|thread_local|tls|exception pass 3 of 3. guest_pthread_start and guest_pthread_frame_chain pass 5 of 5.
  • Red checks on guest_pthread_start, 3 runs each:
Edit Result
the capture in the Windows scePthreadSelf adoption removed fails: Adopted thread host MXCSR was corrupted upon scePthreadExit (passed on the previous head)
Thread.cpp from main fails: Guest thread MXCSR does not have FTZ and DAZ bits set
the restore in scePthreadExit removed fails: Worker thread host MXCSR was not restored upon guest return
the restores in RunThread and the Windows StartNativeThread removed fails: the same message
the capture in the Windows StartNativeThread removed passes, as before: every Windows thread starts at 0x1f80

check_conventions.py --base main passes.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the review queue Next in the maintainer review queue: checks pass, no open change request, no conflict label Nov 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs libkernel review queue Next in the maintainer review queue: checks pass, no open change request, no conflict unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants