Skip to content

fix(libkernel): report thread guard size from attribute queries - #6893

Open
MarcZX10 wants to merge 4 commits into
boykopovar:mainfrom
MarcZX10:codex/carry-pthread-guard-size
Open

MarcZX10 wants to merge 4 commits into
boykopovar:mainfrom
MarcZX10:codex/carry-pthread-guard-size

Conversation

@MarcZX10

@MarcZX10 MarcZX10 commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

Carries Hussein Kadhim's two commits from #6891, preserving his original commit authorship. pthread_attr_get_np and scePthreadAttrGet now copy the thread's guard-size attribute into the destination attribute. PthreadPrivate records the value used at creation, including the existing 0x1000 default. This reports the recorded attribute; it does not claim that the host stack applies that guard size.

Adds guest_pthread_guard_size coverage for the default and explicit sizes, for both query exports, while the thread is running and for a pthread_attr_get_np round trip. A caller-supplied stack reports a zero guard size, matching FreeBSD libthr behavior.

The current open #5918 changes the reported bounds of adopted host-thread stacks on Windows. It shares Thread.cpp and test registration, but does not implement either attribute query; the merge preview is clean.

Tested

  • Current main 66e290f1c: python3 tools/check_conventions.py --base upstream/main --head HEAD and git diff --check upstream/main...HEAD pass.
  • Linux x86-64, GCC 12.2, Release, BUILD_TESTING=ON: guest_pthread_guard_size_tests builds successfully. CTest was attempted but the process aborts at the emulator's host CPU guard before assertions: this Xeon E5-1620 v2 lacks LZCNT, BMI1/2, MOVBE, AVX2 and FMA.
  • The issue reporter reports on Windows that the new test fails on main and passes with the change; guest_pthread_attr and guest_pthread_stack also pass. The reporter's probe covers requested sizes 0, 1, 100, 4096 and 65536, default size, running and finished threads, and the round trip. I did not run these Windows tests.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-up work goes in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw; no stub behavior changed
  • No notes, investigation files or images added to the repository
  • No new third-party code
  • Generic behavior, not title-specific
  • Depends on: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

…pthread_attr_get_np

pthread_attr_get_np and scePthreadAttrGet left the guard size of the
destination attribute untouched, so a thread created with a guard size of
0 reported whatever the caller's attribute held. FreeBSD 11 libthr copies
the thread's creation attribute, guard size included.
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 7807 docs/dev/debt/libkernel.md L156-157
PR 8041 core/libs/tests/GuestPthreadGuardSize.cpp core/libs/tests/GuestPthreadGuardSize.cpp

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 4972 core/libs/prx/libkernel/CMakeLists.txt
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 6044 core/libs/prx/libkernel/CMakeLists.txt
PR 6149 docs/dev/debt/libkernel.md
PR 6226 core/libs/prx/libkernel/CMakeLists.txt
PR 6446 docs/dev/debt/libkernel.md
PR 6469 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 6519 docs/dev/debt/libkernel.md
PR 6740 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
docs/dev/debt/libkernel.md
PR 6879 core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 6977 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7049 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 7149 core/libs/prx/libkernel/Pthread/include/Pthread.hpp
core/libs/prx/libkernel/Pthread/src/Thread.cpp
docs/dev/debt/libkernel.md
PR 7192 core/libs/prx/libkernel/CMakeLists.txt
docs/dev/debt/libkernel.md
PR 7201 core/libs/prx/libkernel/CMakeLists.txt
core/libs/prx/libkernel/Pthread/src/Thread.cpp
PR 7332 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 7509 docs/dev/debt/libkernel.md
PR 7605 docs/dev/debt/libkernel.md

@Dextroid17

Copy link
Copy Markdown
Contributor

@MarcZX100 thanks for carrying this. One case differs from the FreeBSD 11.4 libthr behavior the new debt entry cites: a thread created with a caller-supplied stack (scePthreadAttrSetstack / pthread_attr_setstack) reports the attribute's guard size. FreeBSD reports 0 for that thread.

In FreeBSD 11.4, _pthread_create copies the attribute into new_thread->attr and then calls create_stack(&new_thread->attr). When stackaddr_attr is set, that function zeroes the guard size (thr_create.c#L242-L244):

	if ((pattr->stackaddr_attr) != NULL) {
		pattr->guardsize_attr = 0;
		pattr->flags |= THR_STACK_USER;

pthread_attr_get_np copies pthread->attr after that change, so it returns guardsize_attr == 0 for that thread. The caller's own attribute object is not touched (only the thread's copy is), so pthread_attr_getguardsize on it still returns the value that was set. On this PR's head, PthreadPrivate records attr->_guardsize unconditionally (core/libs/prx/libkernel/Pthread/src/Thread.cpp:320). I ran a small probe (I put it in place of GuestPthreadGuardSize.cpp locally) that sets a 256 KiB stack with scePthreadAttrSetstack, creates a thread and queries it with both exports:

user stack (scePthreadAttrSetstack), default guard: pthread_attr_get_np rc=0 guard=0x1000 ; scePthreadAttrGet rc=0 guard=0x1000
user stack (scePthreadAttrSetstack), guard 0x2000: pthread_attr_get_np rc=0 guard=0x2000 ; scePthreadAttrGet rc=0 guard=0x2000

Following FreeBSD, pthread_attr_get_np should report 0x0 here; scePthreadAttrGet reads the same recorded value in this PR. A minimal fix for Thread.cpp:320:

    if (attr) guardSize = attr->stackAddress ? 0 : attr->_guardsize;

With that line the probe reports 0x0 for both user-stack threads and still reports 0x1000 for a library-allocated stack. guest_pthread_guard_size, guest_pthread_attr and guest_pthread_stack still pass (3 runs). A user-stack case in GuestPthreadGuardSize.cpp would cover this. I did not check what the console returns.

Separately, the PR body says the test covers queries made "while the thread is running and after join". GuestPthreadGuardSize.cpp only queries before scePthreadJoin, and nothing calls the query exports after the join.

tree result
head 2b5a3c3, guest_pthread_guard_size pass 3/3; 0/120 failed with 12 copies run in parallel
head with the production change reverted (all, or only the Posix/Attr.cpp, src/Attr.cpp or Thread.cpp line) fails 3/3 in each arm (abort)
head merged onto upstream/main 406fd9e clean merge; guest_pthread_guard_size/_attr/_stack pass 3/3 (git merge-tree is also clean against upstream/main ea4cc11; not run there)
user-stack probe on head reports 0x1000 / 0x2000 (FreeBSD: 0)
user-stack probe with the one-line fix reports 0x0; the three tests above pass 3/3

Setup: Linux x86-64, lavapipe (Mesa 26.2.4) clean env, ctest -V --no-tests=error -R '^guest_pthread_guard_size$', head 2b5a3c3.

AI-assisted: yes

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for the FreeBSD source trace and the user-stack probe. I applied the reported rule: PthreadPrivate now records guard size 0 when stackAddress is supplied, and the focused test creates a caller-stack thread with a nonzero requested guard and checks both query exports return 0 while it runs. I also removed the unsupported “after join” coverage claim from the PR body. git diff --check and conventions pass. I could not locally build the test: the prior CMake directory points at a different worktree, and a fresh configure with system dependencies stops because SDL2 development files are absent. I have not claimed the probe passes locally; your reported focused Linux probe remains the runtime evidence.

@Dextroid17

Copy link
Copy Markdown
Contributor

@MarcZX100 since you couldn't build it locally: I built and ran the new head 2969fb4 on Linux x86-64. guest_pthread_guard_size (now including your caller-stack check) passes, and it aborts when only the Thread.cpp line is reverted. CI's linux/windows jobs were skipped on this head.

tree                                              -> result
2969fb45e                                         -> guest_pthread_guard_size, guest_pthread_attr, guest_pthread_stack: 3/3 pass each
2969fb45e, Thread.cpp line reverted (test kept)   -> guest_pthread_guard_size aborts 3/3 (attr and stack still pass)
2969fb45e merged with main a7f108030              -> all three: 3/3 pass each
guard_size binary, 12 copies at once x 10 rounds  -> 0/120 fail on 2969fb45e, 0/120 on the merge

My user-stack probe, which queries from the creating thread rather than from inside the worker, also reports guard 0 from both pthread_attr_get_np and scePthreadAttrGet, for the default guard and for 0x2000 requested (a library stack still reports 0x1000).

Setup: the 3x rows ran ctest -V --no-tests=error -R '^<test>$' per test in a clean env -i with a fresh XDG_CACHE_HOME per run. The parallel rows and the probe ran the guest_pthread_guard_size_tests binary directly, also under env -i.

AI-assisted: yes

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for the current-head run and the creating-thread probe. This confirms the caller-supplied stack case returns guard size 0 through both query exports, while the library-allocated stack retains 0x1000. The focused guard-size, attribute, and stack tests pass 3/3 each, and the Thread.cpp negative control fails the guard-size test as expected. I have recorded this runtime confirmation; the hosted Linux/Windows jobs remain skipped on this head.

@cadenasynyster

Copy link
Copy Markdown
Contributor

Windows build check of the current head 2969fb4 (the hosted Windows jobs were skipped on it): the new test does not compile with MinGW.

Windows 11 10.0.26200.9711, WinLibs GCC 15.2.0 posix UCRT r7, Release, BUILD_TESTING=ON, PR merged onto main f3263df (no conflicts):

core/libs/tests/GuestPthreadGuardSize.cpp:76:24: error: 'aligned_alloc' is not a member of 'std'; did you mean 'aligned_union'?

CheckUserStackGuardSize() (added in 2969fb4) allocates the caller stack with std::aligned_alloc and frees it with std::free. The Windows CRT does not provide aligned_alloc, so guest_pthread_guard_size_tests is not built and ctest reports guest_pthread_guard_size as Not Run.

Using C++17 aligned new/delete instead builds and passes:

+#include <new>
...
-    void* stack = std::aligned_alloc(0x1000, USER_STACK_SIZE);
+    void* stack = ::operator new(USER_STACK_SIZE, std::align_val_t{0x1000});
...
-    std::free(stack);
+    ::operator delete(stack, std::align_val_t{0x1000});

With that change on the same merge: guest_pthread_guard_size, guest_pthread_attr and guest_pthread_stack pass 3/3 each (ctest --repeat until-fail:3). I did not run the Thread.cpp negative control on Windows, and did not check the change on Linux.

AI-assisted: yes

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for catching the MinGW portability issue and for testing the aligned allocation replacement. I applied it in ece11f830: the caller stack now uses C++17 aligned operator new/operator delete with <new>, avoiding std::aligned_alloc and std::free on Windows. The focused guest_pthread_guard_size_tests target builds on Linux with GCC 12.2. I attempted the CTest case too, but this host aborts at the emulator CPU-feature guard (missing LZCNT, BMI1/2, MOVBE, AVX2 and FMA), before the test assertions. Your Windows 3/3 result covers the exact allocation change.

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

Linux runtime run of the current head ece11f830, since the author's host stops at the CPU-feature guard and the MinGW check did not cover Linux.

Machine: Linux x86_64, i7-8700, Ubuntu 26.04, GCC 15.2, Release, BUILD_TESTING=ON.
Head ece11f830 merged onto main 20a84154f (clean merge).

tree                                              -> result
ece11f830 + main                                  -> guest_pthread_guard_size, guest_pthread_attr, guest_pthread_stack: 3/3 pass each
same, Thread.cpp guard line reverted (test kept)  -> guest_pthread_guard_size aborts (attr and stack still pass)
guard_size binary, 12 copies at once x 10 rounds  -> 0/120 fail

So the aligned operator new/operator delete stack builds and runs on Linux, and the caller-stack case still catches the Thread.cpp change.

Setup: ctest --no-tests=error -R '^<test>$' --repeat until-fail:3 per test under env -i with a fresh XDG_CACHE_HOME; the parallel row ran guest_pthread_guard_size_tests directly.

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

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for running the current head on Linux. The three focused tests pass 3/3 each, and the reverted Thread.cpp control fails only the guard-size check as intended. That confirms the aligned stack allocation works on Linux as well as the earlier Windows result. I’ve recorded this validation on the PR.

AI-assisted: yes (Codex); report reviewed.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for the independent Linux run on the current head ece11f830. The three focused tests passed 3/3 each, the reverted Thread.cpp control failed at the expected guard-size assertion, and the 120 parallel runs had no failures. I have recorded this as an independent Linux runtime result; it complements the earlier Windows check.

@LeandroLP23

Copy link
Copy Markdown
Contributor

Windows run of the current head ece11f830: with the aligned operator new change, GuestPthreadGuardSize.cpp now builds with MinGW, and the focused tests pass.

Radeon 860M (RDNA 3.5), Ryzen AI 7 350, Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON. main 6efb7261b with #7432, #6912 and #6893 merged on top (no conflicts, disjoint files).

guest_pthread_guard_size, guest_pthread_attr, guest_pthread_attr_stack and guest_pthread_stack pass, 3 runs of 3 each.

AI-assisted

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for the Windows run on head ece11f830. The MinGW build and 3/3 passes for the guard-size and related pthread tests confirm the aligned stack allocation works there. Together with the Linux focused runs and reverted-code control, this gives us coverage on both Windows and Linux.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants