Skip to content

feat(libc): implement std::_Atomic_assert - #7851

Open
hypermoist wants to merge 6 commits into
boykopovar:mainfrom
hypermoist:feat/libc-atomic-assert
Open

hypermoist wants to merge 6 commits into
boykopovar:mainfrom
hypermoist:feat/libc-atomic-assert

Conversation

@hypermoist

Copy link
Copy Markdown
Contributor

What

Implements std::_Atomic_assert(const char* expression, const char* function) (_ZSt14_Atomic_assertPKcS0_, u2tMGOLaqnE), which was a NotImplemented stub in Export.cpp. It now sits next to _Assert in src/StdioExtras.cpp and follows the same pattern: write the message to stderr, then abort.

The message format comes from the retail libc.prx (J03848509), which writes expression, then " in function " and function when function is non-null, then "\n". The console brackets this with sceKernelDebugRaiseExceptionOnReleaseMode(0xa0020007, 0) and sceKernelDebugRaiseException(0xa0020007, 0) followed by ud2. libc can't reach libkernel, so this uses std::abort() like _Assert. A null expression prints nothing for it instead of faulting.

Tested

Linux (CachyOS, GCC Release). The new guest_atomic_assert test forks, calls the export with and without a function name, and checks the exact stderr text and SIGABRT. All 474 guest_* tests pass. The NID drops off the libc todo list in tools/progress.py. tools/check_conventions.py passes. tools/pr_overlap.py --branch reports no line overlap. The only shared hunk is the libc test CMake, which #6044 moves along with every other test.

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: none
  • GPUs tested: n/a
  • AI-assisted: yes

🤖 Generated with Claude Code

Write the console's message, the expression followed by " in function"
and the function name when one is given, to stderr and abort.

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

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Progress report (fe2b52c → 5ff0e82)

📈 System libraries: 49.34% (+0.0076%, +1 implemented functions)

✅ 1 implemented
Library Name
libc _ZSt14_Atomic_assertPKcS0__nid_postfix

Changed libraries (implemented exports)

Library Before After Change
libc 1396/1405 (99.36%) 1397/1405 (99.43%) +0.07 pp

@github-actions

github-actions Bot commented Oct 19, 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 9087 docs/dev/debt/libc.md L93-94

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 5021 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 5303 docs/dev/debt/libc.md
PR 6819 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7081 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7386 docs/dev/debt/libc.md
PR 7447 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 7715 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 8150 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 8965 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 9340 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9447 docs/dev/debt/libc.md
PR 9526 docs/dev/debt/libc.md
PR 9929 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 10113 docs/dev/debt/libc.md
PR 10226 docs/dev/debt/libc.md
PR 10245 docs/dev/debt/libc.md
PR 10250 docs/dev/debt/libc.md
PR 10253 docs/dev/debt/libc.md
PR 10265 docs/dev/debt/libc.md
PR 10274 docs/dev/debt/libc.md
PR 10325 docs/dev/debt/libc.md
PR 10328 docs/dev/debt/libc.md
PR 10345 docs/dev/debt/libc.md
PR 10349 docs/dev/debt/libc.md
PR 10405 docs/dev/debt/libc.md
PR 10506 docs/dev/debt/libc.md
PR 10510 docs/dev/debt/libc.md
PR 10512 docs/dev/debt/libc.md
PR 10565 docs/dev/debt/libc.md
PR 10987 docs/dev/debt/libc.md
PR 11260 docs/dev/debt/libc.md
PR 11262 docs/dev/debt/libc.md
PR 11266 docs/dev/debt/libc.md
PR 11332 docs/dev/debt/libc.md
PR 11632 docs/dev/debt/libc.md
PR 11634 docs/dev/debt/libc.md
PR 11724 docs/dev/debt/libc.md
PR 11726 docs/dev/debt/libc.md
PR 11831 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 11907 docs/dev/debt/libc.md
PR 12122 docs/dev/debt/libc.md
PR 12397 docs/dev/debt/libc.md
PR 12514 docs/dev/debt/libc.md
PR 12560 docs/dev/debt/libc.md
PR 12574 docs/dev/debt/libc.md
PR 12593 docs/dev/debt/libc.md
PR 12622 core/libs/prx/libc/src/StdioExtras.cpp
docs/dev/debt/libc.md
PR 12627 docs/dev/debt/libc.md
PR 12682 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 12688 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 12700 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 12965 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 13119 docs/dev/debt/libc.md
PR 13390 core/libs/prx/libc/CMakeLists.txt
docs/dev/debt/libc.md
PR 13804 docs/dev/debt/libc.md
PR 14212 docs/dev/debt/libc.md
PR 14585 core/libs/prx/libc/CMakeLists.txt
core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md
PR 14610 core/libs/prx/libc/include/Api.hpp
docs/dev/debt/libc.md

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

Reviewed the diff:

  • Replaces the legacy placeholder stub in core/libs/prx/libc/Export.cpp with a clean Dinkumware implementation in StdioExtras.cpp, writing expression and optional function to stderr before calling std::abort().
  • Removes the non-compliant legacy comment from Export.cpp.
  • GuestAtomicAssert.cpp uses pipe() / fork() / waitpid() to verify child process abort behavior and output formatting, and is correctly gated under if(UNIX) in libc/CMakeLists.txt so Windows builds are unaffected.
  • Zero comments added in code; Conventional Commits title; all checklist items verified.

Ready to merge.

AI-assisted: yes

@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@Athen2045

Athen2045 commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Windows run of 727c28a merged onto main 3611cda (Windows 11 26300, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON):

  • Build: clean, with no warnings in the changed files.
  • guest_atomic_assert is not registered on Windows. It sits in the if(UNIX) block and uses fork, so the export has no automated Windows coverage. I called it from a small harness instead (not committed). The harness loads the patched libc.prx, resolves u2tMGOLaqnE, calls it in a child process and captures stderr and the exit code.
    • ("invalid memory order", "atomic_load"): the first stderr line is exactly invalid memory order in function atomic_load.
    • ("invalid memory order", nullptr): the first line is exactly invalid memory order.
    • In both cases libc's Windows crash report follows (FATAL: abort() (SIGABRT) on thread ... and a backtrace), and the process exits with 0xc0000409. _Assert takes the same std::abort() path, so on Windows the whole of stderr differs from what the Linux test expects, but the message line matches.
  • Negative control: against main the same calls end with [libc] unhandled exception 'St13runtime_error' from the NotImplemented stub.
  • Full suite (select_tests.py ctest ... --timeout 120): 1708 of 1713 pass on the merge. The same 5 tests fail on main on this machine: conventions_env_reference, conventions_blob_reads, windows_import_modules, agc_driver_msaa_dcc_clear_4 and agc_driver_pass_input_hazard.

If Windows coverage is wanted, a variant could spawn itself with CreateProcessW, as GuestFileLock.cpp and GuestHeapRounding.cpp do, and check the first stderr line and the 0xc0000409 exit.

AI-assisted: yes (Claude Code). The results above were run on this machine.

Edit: corrected the base commit from 585075c to 3611cda, the main these runs were actually built on. The results are unchanged.

@hypermoist

Copy link
Copy Markdown
Contributor Author

@Athen2045 thanks for the Windows run and the negative control. Good to have confirmation that the message line matches on Windows, with the 0xc0000409 exit coming from the same std::abort() path _Assert takes. You're right that guest_atomic_assert is Unix-only because of fork. I don't have a Windows toolchain here, so I'd rather not push a CreateProcessW variant I can't build or run. If you'd like to add it along the lines of GuestFileLock.cpp, checking the first stderr line and the 0xc0000409 exit, I'd welcome it here or as a follow-up. Otherwise I'll leave the PR as it is.

@DavidSamir

Copy link
Copy Markdown
Contributor

Hi @hypermoist, a merge-order note for #9583 (test(libc): run guest_fopen_errors on Windows). It moves guest_fopen_errors out of the if(UNIX) block in core/libs/prx/libc/CMakeLists.txt, right next to where this PR adds guest_atomic_assert, so the two conflict textually in that one hunk and nowhere else. Proposal: neither depends on the other. Whichever merges second keeps guest_atomic_assert inside if(UNIX) and guest_fopen_errors outside it. Does that work for you? I'll record the agreed order in #9583's checklist.

AI-assisted: yes

@hypermoist

Copy link
Copy Markdown
Contributor Author

@DavidSamir that works for me. Neither PR depends on the other, and whichever merges second keeps guest_atomic_assert inside if(UNIX) (it uses fork) and guest_fopen_errors outside it. If #9583 lands first, I'll merge main into this branch and resolve it that way.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 24, 2026
hypermist and others added 2 commits October 25, 2026 13:06
…sert

Resolved the conflict in core/libs/prx/libc/Export.cpp by dropping the stub, as this branch implements it in StdioExtras.cpp.

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

verusferro commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

I checked this against the SDK 7.00 libc.prx that PPSA06416 ships (u2tMGOLaqnE at 0x80170, 111 bytes). The message format matches your description, and so does the call order: sceKernelDebugRaiseExceptionOnReleaseMode(0xa0020007, 0) comes first, and sceKernelDebugRaiseException(0xa0020007, 0) followed by ud2 comes last. I found two differences:

  • The fputs calls write to the guest _Stderr stream (H8AprKeZtNg, the object at 0x110480) through the module's own fputs (0x44a0), not to the host's stderr. On the console, a title that redirected stderr with freopen, or buffered it with setvbuf, gets the message in that stream.
  • The module's fputs takes the file lock and then reads the first byte of the string without a null check. So a null expression faults on the console, where this PR prints nothing. Elsewhere the repo throws in that case (for example the null text in inet_aton).

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.

I’m updating my earlier approval after checking the SDK 7.00 behavior report in the disassembly comparison against the current stream wrappers.

[P2] Write the diagnostic through the guest stderr stream. The implementation calls host std::fputs(..., stderr), which bypasses the guest _Stderr FileStream. The reported retail routine calls the module’s fputs using _Stderr, so guest freopen/setvbuf state is honored. In this tree __stderrp_nid_postfix points at _Stderr_nid_postfix, and fputs_nid_postfix routes through the guest FileStream; please use that guest stream path (including its status/locking behavior) before aborting. Add a regression that redirects guest stderr and verifies the assertion text reaches the redirected stream; the existing fork test only redirects the host descriptor and therefore cannot detect this mismatch.

Please also resolve the null expression behavior against the same SDK reference: this PR substitutes an empty string, whereas the reported console routine passes the pointer to fputs. Don’t add a made-up success or message for that input; document and test the supported behavior consistent with the project’s conventions and the observed ABI.

hypermoist added 3 commits October 27, 2026 18:35
Route diagnostic output through the guest _Stderr stream rather than
host stderr, honoring redirection and buffering. Pass expression directly
to fputs so null input faults/throws instead of substituting an empty
string, document the technical debt, and test guest stderr redirection
and null input behavior.

Assisted-by: Antigravity:gemini-3.8-flash
…sert-work

# Conflicts:
#	core/libs/prx/libc/CMakeLists.txt
#	docs/dev/debt/libc.md
@hypermoist

Copy link
Copy Markdown
Contributor Author

@MarcZX10 @verusferro thanks for the reviews. Both requests are addressed in 5ff0e82:

  • Guest stream routing. The diagnostic now routes through guest _Stderr via fputs_nid_postfix(..., __stderrp_nid_postfix) and fflush_nid_postfix(__stderrp_nid_postfix), preserving guest freopen and setvbuf state before aborting.
  • Redirection test. GuestAtomicAssert.cpp adds a test verifying that when guest __stderrp_nid_postfix is redirected via freopen_nid_postfix, the assertion text is written to the redirected file upon process abort.
  • Null expression. Removed the empty-string fallback. Passing a null expression directly routes to fputs_nid_postfix, which throws std::runtime_error("fputs: null string") (tested in GuestAtomicAssert.cpp). The behavior is recorded in docs/dev/debt/libc.md.
  • Main sync. Merged current main and resolved the overlap with test(libc): run guest_fopen_errors on Windows #9583 (keeping guest_atomic_assert inside if(UNIX) and guest_fopen_errors outside). guest_atomic_assert passes and check_conventions.py is clean.

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

Re-checked — new head, looks good

The new head resolves the two earlier findings: the diagnostic uses guest _Stderr, redirected guest stderr is tested, and the null-expression path is no longer replaced with an empty string.

What I ran

Exact head: 5ff0e821f6fea62e78559d73572821bd7e2e844f. Current upstream base: ba5b5330b37724bca4b99f30faab504653635a04. The current-main merge preview completed cleanly (tree 92810a64d035efd27bb35de0da1cedfc204175e3). Environment: Linux x86-64, GCC 12.2.0.

g++ -std=c++20 -I/tmp/AnyPS5-mention-head.ciGWED -Icore/libs -fsyntax-only /tmp/AnyPS5-mention-head.ciGWED/StdioExtras.cpp
g++ -std=c++20 -I/tmp/AnyPS5-mention-head.ciGWED -Icore/libs -fsyntax-only /tmp/AnyPS5-mention-head.ciGWED/GuestAtomicAssert.cpp
git diff --check fe2b52c7e431969782351d7d53fe0f7f9e0f344b..refs/review/mention-7851 -- core/libs/prx/libc/src/StdioExtras.cpp core/libs/tests/GuestAtomicAssert.cpp docs/dev/debt/libc.md

Results

Check Result
GCC syntax-only: StdioExtras.cpp, GuestAtomicAssert.cpp Passed locally
git diff --check Passed locally (exit 0)
Current-main merge preview Clean; tree SHA above
guest_atomic_assert Author reports passing on this exact head in the follow-up
Hosted checks Eligibility, conventions, labels, report, and relinker jobs passed; Linux and Windows product jobs were skipped
Local CTest Not run: the existing build-review configuration is relinker-only and has no guest_atomic_assert_tests target
Raw local check output
StdioExtras.cpp: PASS
GuestAtomicAssert.cpp: PASS
7851 diff-check exit=0
92810a64d035efd27bb35de0da1cedfc204175e3
ninja: error: unknown target 'guest_atomic_assert_tests'
No tests were found!!!

Negative control

Assertion Code mutation Result
Not run No mutation-based negative control was run on this head Not exercised

Findings

Blocking

None found in the reviewed changes.

Non-blocking

The full local target and product build were not available in this relinker-only build configuration. The author reports the focused test on the exact head; hosted product build jobs were skipped.

Sources

Not verified

I did not run the focused test or a full product build locally, and did not verify console behavior beyond the linked SDK 7.00 evidence recorded by the PR.

@MarcZX10

Copy link
Copy Markdown
Contributor

Correction — follow-up link fixed

I mistyped the issue-comment ID in my review; the author's exact-head test report is here. The review decision and other reported results are unchanged.

@blawesom

Copy link
Copy Markdown
Contributor

Static review plus an independent Linux run (CachyOS, kernel 7.2.10, GCC 16.2.1, Ninja, Release, BUILD_TESTING=ON, Ryzen 7 9800X3D). No findings.

What I checked

  • The old _ZSt14_Atomic_assertPKcS0__nid_postfix stub (NotImplemented_nid_no_patch(__func__)) is replaced by a real [[noreturn]] implementation matching the mangled-libunwind name's intent (std::_Atomic_assert contract): write the assertion message to the guest __stderrp stream, then abort(). The debt note honestly records the only divergence left (a null expression throws std::runtime_error("fputs: null string") here instead of faulting as the console's libc.prx does — noted from disassembly, not measured on a console).
  • The test covers the interesting cases in isolation: named message (expression+function, expects exact "invalid memory order in function atomic_load\n"), anonymous (function == nullptr, expects "invalid memory order\n"), after freopening the guest __stderrp stream to a real file (expects the redirected content exactly), and the null-expression path (expects the recorded runtime_error). Each runs in a forked child (parent reads the child's stderr through a pipe) so SIGABRT truncation doesn't kill the suite — good hygiene, matches how similar POSIX-fixture tests in this tree handle process death.

Focused test run

tree result
this head 5ff0e821f6 1/1 guest_atomic_assert Passed (0.00s)

The CMake entry is scoped if(UNIX) (test uses fork/pipe/waitpid), so this fixture is Linux/macOS-only by design; Windows uses the same StdioExtras.cpp code but has no dedicated fixture here. Not an AGC/shader/VideoOut change — no paired VVL run required.

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed docs 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 docs libc needs rebase Conflicts with main, rebase needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants