Skip to content

feat(libSceLibcInternal): implement the num_get<char> and num_get<wchar_t> get members - #15788

Closed
B-Howell wants to merge 1 commit into
boykopovar:mainfrom
B-Howell:feat/r4-num-get-members
Closed

B-Howell wants to merge 1 commit into
boykopovar:mainfrom
B-Howell:feat/r4-num-get-members

Conversation

@B-Howell

@B-Howell B-Howell commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

What

The 22 public get overloads of num_get<char, istreambuf_iterator<char>> and num_get<wchar_t, istreambuf_iterator<wchar_t>> in libSceLibcInternal threw from the Pending3 placeholders in Err.cpp. They now return do_get through the facet's own vtable. These are the calls std::istream and std::wistream operator>> make through use_facet<num_get<...>> for bool, the integer types, the floating types and void*.

Value type Slot char NID (was) wchar_t NID (was)
bool 0x20 OWO5cpNw3NA (Pending3_056) vW-nnV62ea4 (Pending3_081)
unsigned short 0x28 gozsp4urvq8 (Pending3_063) bZ+lKHGvOr8 (Pending3_088)
unsigned int 0x30 N1VqUWz2OEI (Pending3_060) Y3hBU5FYmhM (Pending3_085)
long 0x38 I2UzwkwwEPs (Pending3_061) -m2YPwVCwJQ (Pending3_086)
unsigned long 0x40 2bfL3yIBi5k (Pending3_062) 94ZLp2+AOq0 (Pending3_087)
long long 0x48 4hiQK82QuLc (Pending3_064) cG5hQhjFGog (Pending3_089)
unsigned long long 0x50 eZfFLyWCkvg (Pending3_065) banNSumaAZ0 (Pending3_090)
float 0x58 6RGkooTERsE (Pending3_059) 2mb8FYgER+E (Pending3_084)
double 0x60 mAwXCpkWaYc (Pending3_057) +hjXHfvy1Mg (Pending3_082)
long double 0x68 wUCRGap1j0U (Pending3_058) xLZr4GJRMLo (Pending3_083)
void* 0x70 my9ujasm6-0 (Pending3_055) zomvAQ5RFdA (Pending3_080)

Each NID matches tools/nid_names.py compute_nid of its mangled name, for example _ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERb gives OWO5cpNw3NA. The others differ in the character type (Ic/Iw) and the last letters (t, j, l, m, x, y, f, d, e, Pv). All 22 were checked.

What each get does:

  • [facet.num.get.members]: get returns do_get(in, end, str, err, val). The first microsoft/STL commit does the same for all eleven overloads (xlocnum L308-L361): return do_get(_First, _Last, _Iosbase, _State, _Val);.
  • The forwarder calls the slot for its value type in the vtable of the facet it is given, so a derived facet's override is reached.

The slot order is an inference. The console's libSceLibcInternal.sprx was not read. The standard synopsis ([locale.num.get]) declares do_get for bool, long, long long, unsigned short, unsigned int, unsigned long, unsigned long long, float, double, long double and void*. This PR uses the microsoft/STL (Dinkumware) order instead, the order of the do_get declarations in xlocnum L365-L636: bool, unsigned short, unsigned int, long, unsigned long, long long, unsigned long long, float, double, long double, void*, starting after the four FacetVtable slots at 0x20. The evidence for that choice is indirect:

  • main's GuestLocale::NumPutVtable, which libc hands to guests as g_numPutVtable (LocaleSupport.cpp), already lays out num_put in the microsoft/STL order (putLong, putUnsignedLong, putLongLong), not the standard's (long, long long, unsigned long);
  • libc declares _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE and _ZTVSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE with 17 entries: offset-to-top and RTTI, 4 facet slots and 11 do_get slots. That fixes the count, not the order.

If the console's order differs, the forwarders call the wrong do_get. The debt entry says so, and the test pins every slot, so a later change to the order fails it.

Changes:

  • GuestLocale.hpp: new GuestLocale::InputIterator, an opaque, trivially copyable 16-byte value for istreambuf_iterator, with static_asserts on its size and triviality. Nothing reads its fields. istreambuf_iterator<char> and istreambuf_iterator<wchar_t> are both 16 bytes (pointer, bool, element padded; the members are _Strbuf, _Got and _Val in streambuf L519-L521) with no user-declared copy constructor or destructor, so both are passed and returned in two integer registers like this struct.
  • GuestLocale.hpp: new GuestLocale::NumGetVtable with all eleven slots, a static_assert on every slot offset (0x20 to 0x70) and on the size (0x78). The ios_base::iostate& argument is _Iosb<int>::_Iostate& in the mangled names and is passed as std::int32_t*.
  • New NumGet.cpp with the 22 forwarders, and the 22 placeholders removed from Err.cpp.
  • docs/dev/debt/libSceLibcInternal.md: the 22 per-NID "prototype and behavior are unverified" entries are removed and one entry records what is still open.

The InputIterator block in GuestLocale.hpp (and its <type_traits> include) is byte-for-byte the block of the time_get and money_get/money_put PRs from the same batch, at the same place, so the three merge in any order. git merge-tree --write-tree of each pair, of all three, and of each against main is clean, and the merged header has one InputIterator.

Still not done:

  • Neither libc nor libSceLibcInternal builds a num_get facet or implements do_get, so today these calls reach only a facet the title provides. The facet constructors, _Init, _Getcat, _Getifld, _Getffld and the do_get members still throw.
  • No title was seen calling these exports.

Tested

No title was run. I added the guest test guest_libc_internal_num_get (core/libs/tests/GuestLibcInternalNumGet.cpp). It loads libSceLibcInternal, calls each of the 22 exports with a facet whose vtable records every call, and checks:

  • each of the 22 exports calls the do_get slot of its own value type exactly once (every slot of the vtable records a different index, so a forwarder bound to a wrong slot, or a wrong slot order, fails);
  • the facet, both 16-byte iterators word for word, the stream, the state pointer and the value pointer arrive unchanged;
  • the export returns the iterator do_get returns and leaves the state do_get wrote;
  • with a second vtable standing in for a derived facet, every export reaches the derived slot, not a fixed one.
    The header static_asserts pin every slot offset (0x20 to 0x70) and the size.

Results:

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

…ar_t> get members

Each of the eleven num_get<char>::get and eleven num_get<wchar_t>::get
overloads returns do_get through the facet's own vtable, as
[facet.num.get.members] requires. The do_get slots follow the
declaration order of microsoft/STL xlocnum, the order NumPutVtable
already uses for num_put. GuestLocale gains an opaque 16-byte
InputIterator for istreambuf_iterator and the NumGetVtable layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added libc build docs prx open PR limit Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20 labels Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Thank you for your contribution! Authors with no pull requests merged into main can have 1 open pull request at a time, authors with at least 1 merged can have 20, and from 20 merged there is no limit. This PR has been automatically closed and labeled "open PR limit" because that limit was reached. This is not a judgment of the quality of your changes: reopen it once one of your open pull requests is merged or closed.

@github-actions github-actions Bot closed this Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Progress report (b11c7bf → 1842d9b)

📈 System libraries: 26.77% (+0.06%, +22 implemented, -22 removed functions)

✅ 22 implemented
Library Name
libSceLibcInternal sceLibcInternalNumGetBool_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetDouble_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetFloat_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetLongDouble_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetLongLong_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetLong_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetPointer_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetUnsignedInt_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetUnsignedLongLong_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetUnsignedLong_nid_no_patch
libSceLibcInternal sceLibcInternalNumGetUnsignedShort_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetBool_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetDouble_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetFloat_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetLongDouble_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetLongLong_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetLong_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetPointer_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetUnsignedInt_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetUnsignedLongLong_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetUnsignedLong_nid_no_patch
libSceLibcInternal sceLibcInternalWideNumGetUnsignedShort_nid_no_patch
🗑️ 22 removed
Library Name
libSceLibcInternal sceLibcInternalPending3_055_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_056_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_057_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_058_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_059_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_060_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_061_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_062_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_063_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_064_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_065_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_080_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_081_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_082_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_083_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_084_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_085_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_086_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_087_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_088_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_089_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_090_nid_no_patch

Changed libraries (implemented exports)

Library Before After Change
libSceLibcInternal 92/1137 (8.09%) 114/1137 (10.03%) +1.93%

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

Labels

build docs libc open PR limit Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20 prx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant