Skip to content

fix(libkernel): honor caller-provided pthread stacks - #9938

Open
BorchBe99 wants to merge 2 commits into
boykopovar:mainfrom
BorchBe99:pr/caller-provided-stacks-20261022
Open

BorchBe99 wants to merge 2 commits into
boykopovar:mainfrom
BorchBe99:pr/caller-provided-stacks-20261022

Conversation

@BorchBe99

@BorchBe99 BorchBe99 commented Oct 22, 2026 •

Copy link
Copy Markdown

What

scePthreadCreate ignored a stack address set in the thread attributes. A thread created with one now runs on that memory, with the given size, on Linux and Windows. This finishes the caller-stack follow-up from #1988. The caller keeps ownership of the memory. Host startup still runs on the native stack, and TLS stays per host thread.

Caller stacks report guard size 0. Thread identity and the stack's lifetime survive return, explicit exit, cancellation and key destructors. On Windows the TEB stack bounds cover the caller's range while the thread runs and are set back to the native bounds before it ends; existing context-stack registrations and pins stay. When longjmp, siglongjmp or setcontext lands on a caller stack, the TEB gets that stack's range, also when a larger registered context stack contains it.

A caller stack has no guard page below it; the libkernel debt file records this.

Merge order with the five open PRs that share behavior with this one:

If one of you needs another order, reply here and I will change Depends on. #7192 (@Jev1337) and #7565 (@pushkarreddyy) can merge before or after this PR, and so can #14190 (@JJenkx) and #15165 (@ljkx), which share only lines in docs/dev/debt/libkernel.md and .github/sanitizer-known-failures.txt with it; this comment lists what the later one keeps. With #7565, its MXCSR setup moves into this PR's version of Aps5CallGuestEntry_nid_no_patch. #12688, #12700 and #12965 (all @Felipebmaitan) build on #12682 and touch this PR's files only through #12682's code. Since #9482 is merged, the new test's joined-thread case expects ESRCH from pthread_attr_get_np. Since #9573 is merged, this PR also changes its test guest_pthread_create_suspended: two of its threads are created from attributes copied from a live thread, which carry that thread's stack address, so they now get a stack of the test's own (as announced). Since #12897 and #12963 are merged, RunGuestThread calls _setjmp, and scePthreadCreate keeps the pendingException reset before this PR's caller-stack lines.

Tested

The new regression test covers all three setter paths, small stacks with large TLS, detach and reuse, and mixed cleanup chains including libc _Thrd_exit. Threads with and without a caller stack catch a host exception and then unwind with the standard unwinder across the entry trampoline to the host stack. On Windows it also checks the TEB range after a uc_link return onto a caller stack inside a larger registered context stack.

Linux x86_64, GCC 15.3.0. This head merges main at 5cb015ca (2026-11-03). On it the full default build, libs, public_headers and conventions pass, and so do the 9 pthread tests and the 16 neighbouring tests below, with the regression and guest_pthread_create_suspended again in 20 repeats each. The other Linux results are from this PR on 0278ca98 (main on 2026-11-02), before its line in .github/sanitizer-known-failures.txt, which no build compiles and only tools/sanitizer_report.py and its test tools/tests/test_sanitizer_report.py read (that test passes with the line):

  • Full default build, libs and conventions pass. The regression passes, also in 20 repeats, and so do 8 other pthread tests, guest_pthread_create_suspended (also in 20 repeats) and 16 neighbouring tests (libc threads, backtrace, mutex, cancel recovery, user context, memory, fixed mmap, and main's newer tests of thread defaults, handle lifetime, detach attributes and raised exceptions). Under ASan, 6 of 7 pthread tests and a separate range and lifecycle probe pass. The regression aborts in one case: a thread on a caller stack returns through uc_link from a makecontext context on a larger stack that contains its own. setcontext_nid_postfix ends in a [[noreturn]] call since feat(libkernel_sys): finish sched_setscheduler and sigreturn #10838, and ASan, which still takes the caller stack for the current one, ignores the __asan_handle_no_return there and then reports a stack-buffer-overflow on the stale redzones. makecontext and setcontext have no fiber annotations, so this PR lists the test in .github/sanitizer-known-failures.txt; tools/sanitizer_report.py then counts that run as a known failure. The ASan build of this PR on b90d009d passes with the same ASan runtime, and main's own uc_link tests pass under ASan with and without this PR.
  • Full suite: of 3,492 registered tests, 3,448 pass, 43 are skipped and 1 fails (CTest exit 8): agc_driver_scattered_runs needs a udmabuf limit below two chunks and fails the same way on a clean build of 0278ca98. tools/check_skips.py rejects 3 skips: agc_driver_sparse_dma_buf_import and agc_host_import_sparse skip because this host's udmabuf size_limit_mb is above 1024, and agc_driver_optimize_on_fiber skips in a default build, which leaves ANYPS5_ENABLE_SPIRV_TOOLS off.

Windows 11 x64, local native run (not CI), this PR on 0278ca98, the tree of the Linux results above:

  • WinLibs GCC 15.2.0 (DWARF exceptions) and MSYS2 UCRT64 GCC 16.2.0 (SEH exceptions): in both, the regression (also in 20 repeats), 20 other pthread, libc-thread and timed-join tests including guest_pthread_create_suspended, and 2 user-context tests pass. The WinLibs run also covers the full build, libs, conventions and 2 memory tests.
  • Full suite (WinLibs): of 3,622 registered tests, 2,788 pass, 829 are skipped and 5 fail (CTest exit 8). vulkan_validation_gate_reports, main_sync_renames_test_feature_rename_with_detection_disabled, main_sync_renames_test_main_rename_with_detection_disabled and check_compile_test_windows_gcc_response_text_mode_is_not_reinterpreted fail the same way on a clean build of 0278ca98. pe_import_first timed out once at 10.02 s against its 10 s limit and passes when run alone, with and without this PR. tools/check_skips.py --job windows rejects 3 skips: guest_fopen_errors, which the clean build of 0278ca98 skips the same way, and 2 that come from this laptop: long_paths (long paths are disabled in the registry) and guest_video_out_high_frame_rate (the display has only 60 Hz modes).

Broken versions of the change fail the regression. On Linux, with the trampoline's CFI damaged (CFA without the final +16, or no CFA expression), it aborts at the unwind check. On Windows (WinLibs, tree ebf12e33), if GuestStackSwitch_nid_no_patch keeps the current TEB range whenever it already contains the new stack pointer, it fails at the uc_link check.

Not covered: Windows was not run on the merge with 5cb015ca, which brings #14402's thread-name change into StartNativeThread. The host-bounds check runs on the main test thread and does not see a worker's final TEB state. macOS takes the same stack switch as Linux but was not built or run. Project CI, game compatibility and performance are not tested.

Checklist

🤖 Generated with Claude Code

@MarcZX10

Copy link
Copy Markdown
Contributor

I reviewed the caller-stack lifecycle across attribute capture, Linux/Windows thread startup, guest entry trampoline, return/exit/cancellation cleanup, stack-bound tracking, and the new lifecycle/unwind tests. The implementation consistently keeps host startup/TLS on the native thread, switches only for the guest entry, and restores native bounds before teardown; the test matrix described covers the main return, detach/reuse, cleanup and unwind cases. I ran git diff --check upstream/main...refs/review/9938 and python3 -I tools/check_conventions.py --base upstream/main --head refs/review/9938; both pass. Hosted conventions, intake and labels pass, but all build/test jobs are skipped by policy for this PR, so the extensive Linux/Windows runs in the description are contributor-reported, not hosted CI results. I could not run local C++ builds because the root filesystem is full.

Current main is 262 commits ahead of the merge base and has changed the shared libkernel test CMake file and debt entry; the pthread implementation paths themselves are unchanged. Please refresh those shared files before merge and preserve current registrations/debt. The listed adjacent PRs warrant the stated merge coordination. I found no independent blocker in this pass.

@github-actions

github-actions Bot commented Oct 22, 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 3965 core/libs/prx/libc/include/GuestStacks.hpp
core/libs/prx/libc/src/GuestStacks.cpp
PR 3986 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 4563 core/libs/prx/libkernel/CMakeLists.txt
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/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 6149 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 6740 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
docs/dev/debt/libkernel.md
PR 6936 core/libs/prx/libkernel/CMakeLists.txt
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/libc/include/GuestStacks.hpp
core/libs/prx/libc/src/GuestStacks.cpp
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7156 core/libs/prx/libkernel/CMakeLists.txt
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7447 docs/dev/debt/libkernel.md
PR 7464 docs/dev/debt/libkernel.md
PR 7532 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7565 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7645 core/libs/prx/libkernel/CMakeLists.txt
PR 7804 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 8463 core/libs/prx/libkernel/CMakeLists.txt
PR 8497 docs/dev/debt/libkernel.md
PR 8592 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 8605 docs/dev/debt/libkernel.md
PR 9019 core/libs/prx/libkernel/CMakeLists.txt
PR 9156 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9199 docs/dev/debt/libkernel.md
PR 9242 docs/dev/debt/libkernel.md
PR 9293 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9297 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9302 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9326 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
docs/dev/debt/libkernel.md
PR 9383 core/libs/prx/libkernel/CMakeLists.txt
PR 9389 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9557 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9566 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 9843 docs/dev/debt/libkernel.md
PR 9947 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10025 docs/dev/debt/libkernel.md
PR 10192 docs/dev/debt/libkernel.md
PR 10319 docs/dev/debt/libkernel.md
PR 10442 docs/dev/debt/libkernel.md
PR 10558 docs/dev/debt/libkernel.md
PR 10645 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10749 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 10806 core/libs/prx/libkernel/CMakeLists.txt
PR 11002 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11215 docs/dev/debt/libkernel.md
PR 11219 docs/dev/debt/libkernel.md
PR 11223 docs/dev/debt/libkernel.md
PR 11247 docs/dev/debt/libkernel.md
PR 11446 core/libs/prx/libkernel/CMakeLists.txt
PR 11624 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 11625 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11628 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 11661 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 11689 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 11698 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 11831 core/libs/prx/libkernel/CMakeLists.txt
PR 11891 core/libs/prx/libkernel/CMakeLists.txt
PR 11894 core/libs/prx/libkernel/CMakeLists.txt
PR 11896 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 11939 core/libs/prx/libkernel/CMakeLists.txt
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 12139 core/libs/prx/libkernel/CMakeLists.txt
PR 12170 docs/dev/debt/libkernel.md
PR 12544 core/libs/prx/libkernel/CMakeLists.txt
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 12688 core/libs/prx/libkernel/CMakeLists.txt
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/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12854 docs/dev/debt/libkernel.md
PR 12965 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 12970 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 13336 docs/dev/debt/libkernel.md
PR 13504 docs/dev/debt/libkernel.md
PR 13506 docs/dev/debt/libkernel.md
PR 13560 docs/dev/debt/libkernel.md
PR 13582 core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 13587 docs/dev/debt/libkernel.md
PR 13695 docs/dev/debt/libkernel.md
PR 13950 docs/dev/debt/libkernel.md
PR 13953 docs/dev/debt/libkernel.md
PR 13964 docs/dev/debt/libkernel.md
PR 13995 docs/dev/debt/libkernel.md
PR 14039 docs/dev/debt/libkernel.md
PR 14043 docs/dev/debt/libkernel.md
PR 14085 docs/dev/debt/libkernel.md
PR 14103 docs/dev/debt/libkernel.md
PR 14105 docs/dev/debt/libkernel.md
PR 14108 docs/dev/debt/libkernel.md
PR 14111 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 14128 docs/dev/debt/libkernel.md
PR 14183 docs/dev/debt/libkernel.md
PR 14190 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 14206 docs/dev/debt/libkernel.md
PR 14212 docs/dev/debt/libkernel.md
PR 14264 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 14272 core/libs/prx/libkernel/CMakeLists.txt
PR 14299 core/libs/prx/libkernel/CMakeLists.txt
PR 14310 docs/dev/debt/libkernel.md
PR 14320 docs/dev/debt/libkernel.md
PR 14324 docs/dev/debt/libkernel.md
PR 14325 docs/dev/debt/libkernel.md
PR 14402 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 14417 docs/dev/debt/libkernel.md
PR 14493 docs/dev/debt/libkernel.md
PR 14497 docs/dev/debt/libkernel.md
PR 14557 core/libs/prx/libkernel/CMakeLists.txt
PR 14566 core/libs/prx/libkernel/CMakeLists.txt
PR 14710 docs/dev/debt/libkernel.md
PR 14756 docs/dev/debt/libkernel.md
PR 14832 docs/dev/debt/libkernel.md
PR 14841 core/libs/prx/libkernel/CMakeLists.txt
PR 14854 docs/dev/debt/libkernel.md
PR 14875 docs/dev/debt/libkernel.md
PR 14876 docs/dev/debt/libkernel.md
PR 14885 docs/dev/debt/libkernel.md
PR 14886 docs/dev/debt/libkernel.md
PR 14894 docs/dev/debt/libkernel.md
PR 14970 core/libs/prx/libkernel/CMakeLists.txt
PR 15030 docs/dev/debt/libkernel.md
PR 15035 docs/dev/debt/libkernel.md
PR 15037 docs/dev/debt/libkernel.md
PR 15040 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 15041 docs/dev/debt/libkernel.md
PR 15046 core/libs/prx/libkernel/CMakeLists.txt
PR 15064 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 15095 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md

@MarcZX10

Copy link
Copy Markdown
Contributor

I reviewed head 65f0cc18fb41d6f3366f95f510df09a0680e62dc; current-main git diff --check and conventions pass. The report gives substantial Linux and Windows local coverage for supplied-stack entry, cleanup/exit, unwind, TEB bounds and ASan switching; project CI and title compatibility are explicitly not claimed. I could not rerun the C++ tests here because the root filesystem has no free space.

The live overlap scan identifies comparable shared hunks with #8041 (guard-size queries/attributes), #8477 and #9160 (thread startup), and #9684 (Linux stack placement); its comparisons against #4563 and #7149 are incomplete because those branches conflict with main. #7149 is particularly important to reconcile manually because both change GuestStacks and Windows pthread stack placement. #9573 also touches the pthread attribute/thread files, while #9482 shares the debt entry. The PR currently suggests merging in any order; please identify which behaviors are independent and preserve/retest them together, and add Depends on where one change is actually required first. I did not find evidence that these are duplicate implementations, and I am not treating file overlap alone as a blocker.

@BorchBe99
BorchBe99 force-pushed the pr/caller-provided-stacks-20261022 branch from 65f0cc1 to b1f5164 Compare October 23, 2026 01:50
@BorchBe99

Copy link
Copy Markdown
Author

Rebased onto 78becbb3; the new head is b1f51649. The patch is unchanged (same git patch-id). The test CMake file and debt entry merged without conflicts, so current registrations and debt entries are kept, and the branch also merges cleanly with main as of b1496678.

Linux on the rebased tree: the full build, the regression (also in 20 repeats), 9 neighbouring tests, ASan and conventions pass. Full suite: 1,826 of 1,848 pass, 19 skip, and the same 3 agc_driver_sample_order* tests fail as on the old base. tools/check_skips.py rejects 2 of the skips: two sparse-import AGC tests, new on main, that skip here because of this machine's udmabuf size limit. I did not rerun Windows because the patch is unchanged.

None of the PRs below needs another one merged first, so Depends on stays none. Neither side calls or relies on code the other adds; the order only decides who resolves the conflict. The description asked their authors about the order and none has asked for a different one so far; if one does, I'll add it to Depends on. Whoever merges second keeps both sides of each conflicting test registration, debt line and declaration, and also:

PR Shared code Keep when merging second Retest together
#8041 guard size in Pthread.hpp and both Attr.cpp guard 0 for caller stacks: _guardsize = thread->callerStack ? 0 : thread->guardSize guest_pthread_guard_size, guest_pthread_provided_stack, guest_pthread_attr, guest_pthread_attr_stack
#7149 GuestStacks, Windows scePthreadCreate, StartNativeThread and scePthreadExit caller-stack threads skip AllocateGuestStack, #7149's stack check and its mapping registration, keep their own address and run through our GuestStackEnter/GuestStackLeave path; our exit longjmp runs before #7149's exit block on Windows: guest_pthread_provided_stack, guest_pthread_stack, guest_user_context, guest_user_context_exit, guest_pthread_frame_chain, guest_kernel_is_stack, guest_pthread_attr_stack, guest_pthread_cancel; on Linux: guest_pthread_provided_stack, guest_pthread_stack
#4563 scePthreadExit, test CMakeLists.txt both lines, ours first, so the thread leaves the caller stack before it waits on Windows: guest_pthread_finished_stack, guest_pthread_provided_stack, guest_pthread_join_detach, guest_pthread_cancel, guest_pthread_stack
#9684 Linux StartNativeThread, test CMakeLists.txt, debt entry its size check, alignment and frame move only for library stacks, inside if (!self->callerStack) on Linux with and without ASan: guest_thread_stack_guard, guest_pthread_provided_stack, guest_pthread_stack, guest_kernel_is_stack, guest_kernel_stack_protect, guest_pthread_frame_chain, guest_pthread_attr_stack, guest_thread_self
#9482 debt entry, pthread_attr_get_np it returns ESRCH for a joined thread, so the joined-thread case in our test (RequireReported(thread, stack, false)) has to expect that and skip its base and size check guest_pthread_attr, guest_pthread_provided_stack, guest_pthread_attr_stack
#7192, #8477, #9160, #9573 adjacent lines (debt entry, macOS block in RunThread, includes, scePthreadCreate) both sides guest_pthread_provided_stack, each PR's own tests, and guest_pthread_attr for #9160 and #9573

Two of them also meet this PR at run time without a conflict: #9160's new pthread_attr_setstackaddr is another way to create a caller-stack thread, and with #9573 a suspended caller-stack thread waits on its caller stack (on Windows with the caller bounds set and its pages pinned). No test of either PR covers these combinations.

I have read the PR heads but not built the combinations. #7149 with this PR needs a Windows run, mainly to see whether its 256 KiB native stack is enough for caller-stack threads (this PR reserves 1 MiB).

@harisibrar1 harisibrar1 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.

Checked against main 3875f8c and FreeBSD 11.4 libthr (thr_create.c create_stack/_pthread_create, thr_attr.c):

  • The semantics match libthr. A stack given in the attributes gets guard size 0 and is never freed or reprotected by the library. Both setstack and setstackaddr treat the address as the lowest byte, and setstackaddr keeps the attr's current size. The existing EINVAL checks in both Attr.cpp files are unchanged and match.
  • The entry trampoline leaves the guest entry with rsp ≡ 8 mod 16, the same as FreeBSD's cpu_set_upcall (top &~0xf, minus 8). The DW_CFA_def_cfa_expression resolves to the native frame (*(rbp+16)+16), so unwinding back across it reads correctly.
  • Freeing the stack after scePthreadJoin is safe. Join returns only after WaitForSingleObject/pthread_join on the host thread, so the thread has left the caller stack, as with FreeBSD's thr_exit tid wakeup.
  • Small question: the non-Windows StartNativeThread branch is also the macOS path, so macOS now runs caller-stack threads too, while the description and the test registration (if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) cover only Linux and Windows. Could the description mention that macOS gets the same switch untested, or could the test be registered there once macOS links?
  • I couldn't build or run this here, so I can't confirm the Windows TEB and __chkstk behaviour myself. The new debt entry about host code, handlers and probes overrunning below the range reads accurately to me.

Reviewed with AI assistance (Claude Code); the checks above were run locally on my machine.

@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.

The caller-stack implementation and its lifetime/unwind coverage were already reviewed in my earlier comments; the author subsequently rebased to b1f516491827f8e53af5f189d064926a9128f6e2 and reports a passing Linux build, repeated focused tests, neighboring tests, ASan and conventions. Those runs are contributor-reported; hosted product build/test jobs remain skipped.

This branch is no longer current with upstream: tools/check_main_sync.py --base upstream/main --head refs/reviews/pr-9938 reports 1 ahead /278 behind, with Thread.cpp, libkernel/CMakeLists.txt, and docs/dev/debt/libkernel.md changed on both sides since the merge base. The merge is clean, but #9960 has changed Thread.cpp since this branch was based. Please refresh against current main, preserve the caller-stack lifecycle alongside that thread-startup work, and rerun the focused Linux/Windows stack and unwind tests plus conventions on the refreshed head.

The existing overlap feedback on #8041, #7149, #4563 and #9684 also still needs an agreed integration order/dependency where the shared behavior requires it; a general “whichever is ready” statement does not identify dependencies among those specific changes. Please record the agreed order in the checklist. This request is for the repository's current-main and overlap rules, not queue volume.

@BorchBe99
BorchBe99 force-pushed the pr/caller-provided-stacks-20261022 branch from b1f5164 to dca8f5b Compare October 23, 2026 20:18
@BorchBe99

Copy link
Copy Markdown
Author

Thanks for checking it against libthr.

macOS: yes, the non-Windows branch of StartNativeThread is its path too, so a macOS thread with a caller stack switches to it the same way. I have no Mac to build or run it on, so I added macOS to "Not covered" in the description. The test stays registered for Linux and Windows; once #8658 (macOS host support) is in, it can be added for macOS after a run there.

Windows: the TEB bounds were checked in the native runs under Tested, with WinLibs and with MSYS2 UCRT64. __chkstk has no test of its own.

@BorchBe99

Copy link
Copy Markdown
Author

Refreshed onto 938812df (main on 2026-10-23); it also merges cleanly with main as of b4cafbe2. The rebase changed only context lines. #9960 turns stackLock into a reference that outlives static destruction; this PR does not touch the lock or the code that takes it, so the caller-stack lifecycle is unchanged.

On the new head, Linux passes the full build, libs, conventions, the regression (also in 20 repeats) with its unwind checks, the neighbouring stack and thread tests and the ASan run. Windows passes the regression (also in 20 repeats) and the neighbouring pthread and user-context tests with WinLibs (DWARF) and MSYS2 UCRT64 (SEH); the WinLibs run also passes the full build, libs and conventions. The numbers are in the description.

Order: the description now proposes #8041 and #9684 first, then this PR, then #7149 and #4563, with what each step needs, and the checklist says Depends on: #8041, #9684. @plokmklo, @NevesHenry, @Pouare514, @poonasor: if you need another order, reply here and I will change it.

@valenciana-nc

Copy link
Copy Markdown
Contributor

@BorchBe99 Thanks for spelling out the #9482 interaction in your integration table. I agree with retaining its ended-thread ESRCH behavior and updating the supplied-stack test's joined-thread query rather than requiring it to succeed or reading output attributes after failure.

For order, #9684's author has already agreed to #9482 first (#9684 (comment)); keeping that order also fits this PR's existing dependency on #9684. Your comment permits either order, so I am recording the #9482-first preference on my PR, without adding a runtime dependency or importing your stack implementation. If you need to go first, let me know so the later update preserves the guard and adjusts that combined test.

I checked the current overlap and that #9482 still merges with main, but have not built the combined supplied-stack branch; no new runtime validation of your changes is claimed. AI-assisted coordination.

danielradosa added a commit to danielradosa/AnyPS5 that referenced this pull request Oct 24, 2026
…read stacks

Took the PR as is; kept our IsInitialPthread_nid_no_patch and the created-suspended state next to its scePthreadCreate and include changes (only those two hunks conflicted).
danielradosa added a commit to danielradosa/AnyPS5 that referenced this pull request Oct 24, 2026
…s the threads it creates from pthread_attr_get_np/scePthreadAttrGet attributes a stack of their own, since those attributes now carry the source thread's stack address and boykopovar#9938 honors it, so the copy ran on the stack of the suspended (or sleeping) thread it was copied from and crashed or hung
@BorchBe99

Copy link
Copy Markdown
Author

@zaryabzip, one combination with #9573 that my earlier comment missed. ReportsCreateSuspendedInAttrGet creates threads from what pthread_attr_get_np and scePthreadAttrGet return for another thread. On main that includes the other thread's stack address, and with this PR a new thread runs on the stack address in its attributes. libthr does the same: pthread_attr_get_np copies stackaddr_attr, and create_stack then uses it as the caller's stack. So with both PRs the copies run on the stack of the thread they were copied from.

On Linux, with both PRs merged onto main at b607bfdc, guest_pthread_create_suspended crashed in 3 of 3 runs (once a stack-smashing abort, twice a segfault); with #9573 alone it passed 5 of 5. With danielradosa/AnyPS5@165523e0, which gives the copies their own stacks with pthread_attr_setstack before pthread_create, it passed 5 of 5. Whichever PR merges second needs that change in the test; if that is this one, I will add it.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 25, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@NevesHenry

Copy link
Copy Markdown
Contributor

@BorchBe99 the order in your description is fine with me: #8041 and #9684 first, then this PR. #9684 doesn't depend on this one.

One change for your rebase: #9684 now sizes the alloca in the Linux StartNativeThread from __builtin_frame_address(0) instead of &stackEnd (9c1557e). With ASan's use-after-return detection on, &stackEnd could point into ASan's fake stack, and guest threads crashed. The alignment and that frame move still apply only to library-allocated stacks, as your table says. #9684 is rebased onto main 504ea3b and keeps #9482's debt entry next to its own.

AI-assisted (Claude Code).

@BorchBe99
BorchBe99 force-pushed the pr/caller-provided-stacks-20261022 branch from dca8f5b to 36a6b5c Compare October 26, 2026 04:21
@BorchBe99

Copy link
Copy Markdown
Author

Rebased onto b90d009d (main on 2026-10-25). Apart from context lines, three things changed since the last push:

The branch merges cleanly with main as of 14eb2cda. On Linux and on Windows (WinLibs and MSYS2 UCRT64) the regression passes, also in 20 repeats, and so do guest_pthread_create_suspended and the neighbouring tests. The description has the numbers and the full-suite failures, which a clean b90d009d shares.

@NevesHenry thanks. When I rebase onto #9684, its page alignment and the frame-address alloca stay on library-allocated stacks.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 26, 2026
@adriadam10 adriadam10 self-assigned this Oct 26, 2026
@pushkarreddyy

Copy link
Copy Markdown
Contributor

@BorchBe99 Thanks for the coordination! That merge order works great for #7565, and moving the MXCSR setup into Aps5CallGuestEntry_nid_no_patch sounds good.

@Felipebmaitan

Copy link
Copy Markdown
Contributor

@BorchBe99 I am coordinating #11872 (POSIX read/receive cancellation, head bcf5df1) with this PR under CONTRIBUTING.md.

Proposed order: #11872 first, then #9938. Both branches touch the guest-entry area of Thread.cpp. #11872 uses opaque saved-context identities for deferred I/O delivery and retains its native thread/cancellation lifetime rules. With #11872 first, your later rebase should retain those hooks alongside your caller-stack switch, native-bound restoration and exit/unwind order. Combined checks should include your supplied-stack/unwind/cancel cases and guest_io_context/guest_io_signal cases. This proposal does not change your existing #8041/#9684 ordering; I have not run the combined branch.

Would you agree to that order and record #11872 in your Depends on line for the ordering? If your PR needs to go first, please say so; I can handle the later conflict resolution on #11872 instead. This is a proposal awaiting your confirmation, not an existing agreement.

AI-assisted coordination.

@maskym

maskym commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Windows result for head 36a6b5c6c7, merged into main 75f3702aed (the PR's Tested section names no Windows run).

Host: Windows 11 26300, Intel i7-12700K, WinLibs GCC 15.2.0 (x86_64-ucrt-posix-seh, r6), Release, BUILD_TESTING=ON, Ninja. Build clean.

The tree also carried the open PRs #10615 and #10416, all merged cleanly, so the run is a joint one; the tests of this PR are guest_pthread_create_suspended, guest_pthread_provided_stack. ctest --test-dir build -R "^(guest_cnd_at_thread_exit|guest_dynamic_cast|guest_pthread_create_suspended|guest_pthread_provided_stack)$": 4/4 passed, this PR's in 0.63 s and 0.10 s. Not run on a PS5.

AI-assisted: yes (Claude Code), the runs and the numbers above are from this machine.

@BorchBe99

Copy link
Copy Markdown
Author

@Felipebmaitan agreed: #11872 goes first, and I added it to Depends on. With the current heads, the only textual conflict is its IoPoint and IoWait block next to this PR's three-argument Aps5CallGuestEntry_nid_no_patch declaration; when I rebase onto it, I keep both. On Linux and Windows I then run guest_pthread_provided_stack, guest_pthread_create_suspended and guest_pthread_cancel together with the guest_io_context_* and guest_io_signal_* tests.

@maskym thanks for the Windows run. The Windows runs under Tested (WinLibs and MSYS2 UCRT64) used the same tree as this head.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@BorchBe99 thanks for the careful write-up; the trampoline, exit and TEB handling read correctly to me. One concern before merge: with a caller stack, every host call the guest thread makes (graphics submission and shader recompilation, file I/O, the Vulkan driver) now runs on the caller's memory with no margin or guard page below it. On main the guest window sits inside a native stack with a 1 MiB host margin and the native guard page, so a deep host call faults or fits instead of silently writing into whatever the title placed below its stack. Titles size these stacks for the console's own libraries, not for our host code. Could you add evidence from a title that sets its own thread stacks (the stack sizes it passes and a run with this change), or a way to keep host calls within a margin, or to detect an overrun? We will also run our own title A/B on this head.

@AdnanKhan-ak47

Copy link
Copy Markdown
Contributor

@BorchBe99 merge-order request for #12897, my two-line macOS fix in Thread.cpp:

  • setjmp becomes _setjmp in the native thread's exit point.
  • std::longjmp becomes _longjmp in scePthreadExit.

On Darwin, setjmp/longjmp save and restore the signal mask, and that corrupted other threads' masks after scePthreadExit (details in #12897 and #1). This PR moves that setjmp into RunGuestThread, so the two PRs conflict on that one line. @MarcZX10 asked us to agree on an order.

Proposal: #12897 first, then this PR. It is two lines with no other dependency, and this PR still has open review points. To make the rebase free for you, AdnanKhan-ak47:update/9938-with-12897 (a52db41d53) is your head 36a6b5c6c7 with #12897 merged in, so you can fast-forward to it:

  • The one conflict: your moved setjmp(exitJump) in RunGuestThread becomes _setjmp.
  • Merges cleanly: the _longjmp in scePthreadExit.
  • Both stack kinds covered: on POSIX hosts your native-stack and caller-stack threads both go through RunGuestThread, so that one line covers both.
  • Windows unaffected: it uses the guest jump buffer, which only changes libkernel's emulated mask, so this doesn't touch it.

Tested on macOS (the macOS PR set from #1 plus that branch):

  • guest_pthread_create_suspended passes 20/20, and guest_pthread_cancel and guest_pthread_cancel_recovery pass.
  • With both lines put back to setjmp/std::longjmp, guest_pthread_create_suspended fails.
  • guest_pthread_provided_stack is built only on Windows and Linux. My Linux run of the branch is pending; the box went offline mid-run.

If you'd rather land first, say so: I'll rebase #12897 onto this PR with the same one-line change and rerun. Either way I'll record the order in #12897's Depends on.

Separately, your Depends on lists #11872, which was closed unmerged and superseded by #12682. #12897 merges cleanly with #12682.

@BorchBe99

Copy link
Copy Markdown
Author

@oneandonlydean thanks. I have no run of a title that sets its own thread stacks, so I can't add that evidence before your A/B run. For detection, this PR could protect the lowest whole 16 KiB page inside the caller's range while the thread runs, so an overrun faults as soon as it touches that page instead of writing into the memory below. The guest then has 16 KiB less of the range than it asked for. Should I add that?

@AdnanKhan-ak47 agreed: #12897 goes first, and I added it to Depends on. When I rebase onto it, the setjmp(exitJump) in RunGuestThread becomes _setjmp. Thanks for pointing out #11872; Depends on now names #12682 instead.

@AdnanKhan-ak47

Copy link
Copy Markdown
Contributor

@BorchBe99 #12897 is merged, so this conflicts with main now, in the line we expected. I've refreshed the resolved branch on current main (93dbd0f82a). It's a fast-forward of your head 36a6b5c6c7, so if it saves you time:

git pull https://github.com/AdnanKhan-ak47/AnyPS5.git update/9938-with-12897
git push

The only conflict was in Thread.cpp. Your StartNativeThread is kept, and the setjmp you moved into RunGuestThread is now _setjmp, as #12897 needs. Nothing else of yours changed. I haven't rebuilt it: Thread.cpp on main doesn't compile on macOS, and my Linux box is offline. Ignore this if you'd rather merge it yourself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@BorchBe99

Copy link
Copy Markdown
Author

@AdnanKhan-ak47 thanks for refreshing it. #8477 was merged after your branch and added a conflict in RunThread, so I merged current main (5cb015ca) myself. StartNativeThread and the _setjmp in RunGuestThread match your resolution, and scePthreadCreate keeps #12963's pendingException reset before the caller-stack lines.

@Felipebmaitan

Copy link
Copy Markdown
Contributor

@BorchBe99 confirming the already-recorded #12682-first order from this stack's side. The replacement reads head is now 32169af1dc395b182370cdf973687d313d33bfc4, integrating current main 4f41f4c375f5abb487dbd184fbc150f31994d762; 42-file reads comparison. The #12688/#12700/#12965 layers are propagated without rewriting published history.

I inspected your current db41fa38767f3347af18c7308d517e7359c13950 caller-stack/exit-jump changes. On the later composition, retain RetireIoStack before cleanup/TSD on normal return, explicit exit and cancellation, alongside your caller-stack/native-bound restoration and exit jumps. Saved-context I/O identity and underscore jumps without guest-mask saving remain in reads. Main's new checked getcontext/setcontext copies retain that identity in mc_spare.

This stack's current composed-source tests pass 660 focused CTests plus six socket/shared-memory composition cases on the translated Linux container. That is not a combined #9938 runtime result, a caller-stack title run, or native Windows 22621 hang qualification. Your existing #8041/#9684 and other ordering/review requirements are unchanged; no maintainer approval or combined validation is implied. Your branch was not modified.

AI-assisted: yes.

@BorchBe99

Copy link
Copy Markdown
Author

@Felipebmaitan thanks for confirming the order and for checking the exit paths. I like this project a lot and opened this PR to support it. I can no longer look after it, though, and I am stopping my work on AnyPS5 until further notice. Anyone is free to use, change or take over the changes here; maintainer edits are enabled on the branch.

@github-actions github-actions Bot added libc libkernel build docs ci .github needs rebase Conflicts with main, rebase needed labels Nov 4, 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

build ci .github docs libc libkernel needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.