Skip to content

feat(libc): format num_put<char>::do_put as the console's libc does - #11054

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
verusferro:feat/libc-num-put-do-put
Oct 26, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
verusferro:feat/libc-num-put-do-put

Conversation

@verusferro

@verusferro verusferro commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What

All eight num_put<char>::do_put overloads threw (NumPutMethodUnsupported), so a title that writes a number to a stream (std::ostream << 42) stopped there. They now do what the SDK libc.prx that PPSA06416 ships in sce_module/ (title SDK 7.00) does, read from its disassembly (REVERSE_ENGINEERING, source 2). The module exports them as ke36E2bqNmI (bool), I3+xmBWGPGo, nlAk46weq1w, H2KGT3vA7yQ, Vbeoft607aI (long, unsigned long, long long, unsigned long long), F+cp2B3cWNU, rLiFc4+HyHw (double, long double) and 0xgFRKf0Lc4 (const void*), with the helpers _Iput (0x95710) and _Fput (0x968d0). The structure is Dinkumware's, between the VC12 and current MSVC versions, with Sony's flag values:

flag value flag value
left / right / internal 0x2 / 0x4 / 0x8 showpoint 0x100
dec / oct / hex 0x10 / 0x20 / 0x40 uppercase 0x200
showbase 0x80 showpos 0x400
scientific / fixed / hexfloat 0x800 / 0x1000 / 0x1800 boolalpha 0x8000
  • Integers and pointers: %[+][#] (showpos, showbase), l or ll, then o, x/X or d/u, or %p, into a 64-byte buffer. _Iput keeps a sign, and with hex a 0x, as the prefix.
  • Floating point: the precision is used as is, or 6 under hexfloat. fixed values above 1e10 grow the buffer by |exp2| * 30103 / 100000, plus 50. The format is %[+][#][.*][L] with f (lowercase even with uppercase), a/A, e/E or g/G. Unlike VC12, _Fput takes a sign or else a leading 0x as the prefix, so -0x1.8p+0 gets internal fill after the -, and it skips grouping when the text after the prefix starts with I, N, i or n.
  • Both helpers widen the digits through the stream locale's ctype<char>, put the thousands separator of its numpunct<char> by the grouping, put its decimal point (where localeconv puts the C one), then fill to the width: before the field, after the prefix (internal) or after the field (left), and reset the width to 0.
  • bool without boolalpha calls the facet's own do_put(long) slot; with it, it writes truename() or falsename() with the same fill.

The facets come from the stream's locale as the inlined use_facet does: the facet id (assigned under the libc lock if still 0), the locale's facet vector, then the global locale when the locale is transparent. Missing facets fall back to the C locale: a cached numpunct from _Getcat, and digits left as they are for ctype, since this library has no ctype<char>::_Getcat and the console's C-locale ctype does not change them. numpunct strings are freed with the console's large-allocation rule. GuestLocale.hpp describes the num_put and ctype vtables; the iterator and stream buffer types come from #11044, which this builds on.

Two known differences, recorded in a new debt entry:

  • The text comes from this library's snprintf, which converts floating point exactly and rounds ties to even. The module's converter keeps about 18 exact significant digits and prints later ones from an inexact scaling, so 1e10 at precision 22 is 9999999999.999999999999 there, and -2.5 at precision 0 is -3.
  • A precision so negative that the module's buffer (precision + 50 bytes) cannot hold the result throws here. The module reads stale bytes past what sprintf wrote.

Tested

Ubuntu 24.04 (WSL2) x86-64, GCC 13.3, Release, full build with BUILD_TESTING=ON:

  • The title's libc.prx, converted by the relinker and loaded in a host process, compared against this library's facet with the same fake stream buffer and stream: 502,800 calls per side. They cover every overload, base and float field, showbase/showpos/uppercase/showpoint combination, all four adjustments, widths -1 to 30, precisions -1 to 40, values from 0 and denormals to INT64_MIN, UINT64_MAX, 1e300, 1e4000L, NaN and infinities, a stream buffer with room, two bytes of room with overflow accepting or refusing, a null buffer and a null put pointer, and the module's C locale or a locale with a guest numpunct (, point, . separator, grouping \3\2, a 22-character and a 5000-character name).
    • Integers, bool and pointers match in every call.
    • Floating point matches in every call where the module's sprintf and this library's snprintf print the same text for the same format, precision and value. The 58,880 calls that differ are exactly those where the two sprintf outputs already differ.
    • With grouping \1, both leave inf, -inf and nan ungrouped and group 1.2.3.4,5.
  • guest_locale gains CheckNumPut: 19 calls with strings taken from the module's output, including internal fill with a sign and with 0X, the -0x1.8p+0 prefix, a custom locale with grouping \1\2 (which would split inf without the infinity rule), and the throw for precision -45. Taking 0x as part of the prefix only under hexfloat after the sign (VC12) fails it, and so does grouping infinities.
  • ctest -R '^(guest_locale|guest_strftime|guest_wide_formatting|patched_libraries|conventions_unknown_nids)$': 5/5 pass.
  • tools/progress.py: libc +8 implemented (NumPutBool … NumPutPointer), NumPutMethodUnsupported removed.
  • python3 tools/check_conventions.py --base origin/main: passed. python3 tools/pr_overlap.py: no conflicting lines or same exports. Of the 39 pull requests it could not compare, only feat(libc): add std::__sce_v2::_Random_device, _Fiopen and setw #9526 names one of the types this touches, by creating an IosBase in its own code; none changes num_put, the stream buffer types or this debt entry.

Windows: not run by me. guest_locale passed in @ycbuzi's run of e6c4608736 and in @wachi35676's run of this head 1d31901c4b.

Not tested: macOS, a title that writes numbers to a stream.

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: feat(libc): format time_put<char>::do_put as the console's libc does #11044
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Progress report (0119103 → 1d31901)

📈 System libraries: 49.69% (+0.04%, +9 implemented, -2 removed functions)

✅ 9 implemented
Library Name
libc NumPutBool
libc NumPutDouble
libc NumPutLong
libc NumPutLongDouble
libc NumPutLongLong
libc NumPutPointer
libc NumPutUnsignedLong
libc NumPutUnsignedLongLong
libc TimePutDoPut
🗑️ 2 removed
Library Name
libc NumPutMethodUnsupported
libc TimePutMethodUnsupported

Changed libraries (implemented exports)

Library Before After Change
libc 1345/1356 (99.19%) 1354/1363 (99.34%) +0.15 pp

@github-actions

github-actions Bot commented Oct 25, 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 5021 docs/dev/debt/libc.md
PR 5303 docs/dev/debt/libc.md
PR 6713 docs/dev/debt/libc.md
PR 7081 core/libs/prx/libc/src/LocaleSupport.cpp
docs/dev/debt/libc.md
PR 7386 docs/dev/debt/libc.md
PR 7447 docs/dev/debt/libc.md
PR 7544 docs/dev/debt/libc.md
PR 7648 docs/dev/debt/libc.md
PR 7715 docs/dev/debt/libc.md
PR 8150 docs/dev/debt/libc.md
PR 9371 docs/dev/debt/libc.md
PR 9447 docs/dev/debt/libc.md
PR 9526 core/libs/prx/libc/src/LocaleSupport.cpp
core/libs/tests/GuestLocale.cpp
docs/dev/debt/libc.md
PR 9772 docs/dev/debt/libc.md
PR 9863 docs/dev/debt/libc.md
PR 9929 docs/dev/debt/libc.md
PR 9953 docs/dev/debt/libc.md
PR 9964 docs/dev/debt/libc.md
PR 10002 docs/dev/debt/libc.md
PR 10007 docs/dev/debt/libc.md
PR 10158 core/libs/prx/libc/src/LocaleSupport.cpp
core/libs/tests/GuestLocale.cpp
docs/dev/debt/libc.md
PR 10200 core/libs/prx/libc/src/LocaleSupport.cpp
core/libs/tests/GuestLocale.cpp
docs/dev/debt/libc.md
PR 10234 docs/dev/debt/libc.md
PR 10315 docs/dev/debt/libc.md
PR 10317 docs/dev/debt/libc.md
PR 10321 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 10467 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 10529 docs/dev/debt/libc.md
PR 10565 docs/dev/debt/libc.md
PR 10749 docs/dev/debt/libc.md
PR 11332 docs/dev/debt/libc.md
PR 11341 docs/dev/debt/libc.md

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

Reviewed exact head e6c4608. The eight vtable slots and their argument types match the inspected SDK layout; integer, pointer, floating, boolalpha, locale grouping/decimal-point, fill/adjustment, and width-reset paths are exercised by the new guest_locale cases. The PR documents the observed snprintf rounding differences and oversized negative-precision behavior instead of claiming parity where it differs. The author's 502,800-call comparison against the title's SDK libc.prx is detailed and reports integer/bool/pointer agreement and float differences only where snprintf text differs. I independently ran diff-check, conventions, and GCC C++20 syntax checks for LocaleSupport.cpp and GuestLocale.cpp; all passed. All applicable hosted jobs now pass, including the previously pending macOS Clang relinker; product Linux/Windows jobs are skipped by path policy. I could not run the runtime suite locally because this checkout lacks the SDL2 submodule, so the focused CTest and SDK comparison remain author-reported. I found no blocking issue in the reviewed change.

@ycbuzi

ycbuzi commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

A Windows run of this head: Windows 11 Home 10.0.26200, WinLibs GCC 15.2.0 r7 (MinGW-w64 UCRT, posix-seh), CMake 4.3.1, Ninja 1.13.2, Release. e6c4608736 already contains main d2b3ee3bcc.

A note on the stack: this branch carries #11044 as b698e9d5d0, rebased onto d2b3ee3bcc. #11044's head is still 186fbf3b8f, on 3a2c51afd2. The two commits make the same change; they differ only in their base. Merging #11044's current head onto main and then this branch conflicts in four files: GuestLocale.hpp, LocaleSupport.cpp, tests/GuestLocale.cpp and docs/dev/debt/libc.md. Both sides change the same places in them.

Not run: Linux, macOS, the comparison against the title's libc.prx.

AI-assisted: yes (Claude Code)

The time_put facet's do_put formats % with the modifier and conversion through strftime and writes the result through the output iterator's stream buffer, following the SDK libc.prx: it stores into the put area while room remains, calls overflow otherwise, and marks the iterator failed when overflow returns eof or the buffer is null.
The num_put facet's eight do_put overloads build the console's printf format from the stream flags, format through snprintf and lay out the result as the SDK libc.prx does: thousands separators from the stream locale's numpunct, its decimal point, fill and width with left, right and internal adjustment, true and false names under boolalpha, and the console's prefix and infinity rules for floating point.
@verusferro
verusferro force-pushed the feat/libc-num-put-do-put branch from e6c4608 to 1d31901 Compare October 25, 2026 16:22
@verusferro

Copy link
Copy Markdown
Contributor Author

Thanks for the Windows runs and for catching the stack. #11044's head is now 3338ffefff, and this branch is 3338ffefff + 1d31901c4b, so merging #11044 and then this one applies cleanly. Both commits also moved their GuestLocale.cpp additions off the lines #9456 and #9468 change (details on #11044); git merge-tree of 1d31901c4b with each of them is clean. The code is unchanged, and guest_locale, guest_strftime, patched_libraries and conventions_unknown_nids pass.

@wachi35676

Copy link
Copy Markdown
Contributor

Windows run, which the description doesn't cover: head 1d31901c4bf7 merged onto main 90f918fa7. Windows 11 10.0.26300 with WinLibs GCC 15.2.0, and WSL Ubuntu 24.04 with GCC 13.3, both Release.

  • guest_locale passes on both platforms. With GuestLocale.hpp/LocaleSupport.cpp from main, it aborts.
  • Your module comparison ran on Linux, so I compared the two platforms directly. 36,736 putDouble/putLongDouble calls: 25 doubles (signed zeros, denormals, DBL_MIN, DBL_MAX, 1e22/1e23, inf, NaN of both signs) and 16 long doubles (1e±4000L, LDBL_TRUE_MIN, LDBL_MAX, inf, NaN, ...). Each ran with float field {default, scientific, fixed, hex} × subsets of showpoint/uppercase/showpos/0x8 × precision {-1, 0, 1, 6, 17, 21, 40} × width {0, 30}. The Windows and Linux outputs are byte-identical (md5 04be7aaa33c61832ddb864c6e1ba7ecc for both). For example, 1e4000L in fixed prints 99999999999999999999 followed by zeros on both. So the MinGW guest-printf path and the sysv_abi long double argument give the same results as the Linux build you compared against the module.

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

@oneandonlydean oneandonlydean self-assigned this Oct 26, 2026
@ycbuzi

ycbuzi commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

A Windows rerun of the new head 1d31901c4b, merged onto main 51f5c57dd9 with no conflicts: Windows 11 Home 10.0.26200, WinLibs GCC 15.2.0 r7 (MinGW-w64 UCRT), Release, plain ctest --repeat until-fail:2.

  • guest_locale passes, 2 of 2. The build has no warnings.
  • No negative control: with main's GuestLocale.hpp and LocaleSupport.cpp, tests/GuestLocale.cpp doesn't build ('Streambuf' in namespace 'GuestLocale' does not name a type at :300).

AI-assisted: yes (Claude Code)

@oneandonlydean
oneandonlydean merged commit d6ac53b into boykopovar:main Oct 26, 2026
19 checks passed
@verusferro
verusferro deleted the feat/libc-num-put-do-put branch October 28, 2026 13:07
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.

5 participants