Skip to content

feat(libSceLibcInternal): implement the codecvt<char, char> members - #15721

Closed
B-Howell wants to merge 1 commit into
boykopovar:mainfrom
B-Howell:feat/r2-codecvt-char
Closed

B-Howell wants to merge 1 commit into
boykopovar:mainfrom
B-Howell:feat/r2-codecvt-char

Conversation

@B-Howell

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

Copy link
Copy Markdown
Contributor

What

This implements nine std::codecvt<char, char, _Mbstatet> members in libSceLibcInternal. On main each one threw through NotImplemented_nid_no_patch (sceLibcInternalPending3_027..035 in Err.cpp):

Symbol NID Implementation
_ZNKSt7codecvtIcc9_MbstatetE16do_always_noconvEv 7tIwDZyyKHo libc CodecvtAlwaysNoconv: true
_ZNKSt7codecvtIcc9_MbstatetE5do_inERS0_PKcS4_RS4_PcS6_RS6_ P+q1OLiErP0 libc CodecvtNoConversion: noconv, from_next = from, to_next = to
_ZNKSt7codecvtIcc9_MbstatetE6do_outERS0_PKcS4_RS4_PcS6_RS6_ Uc+-Sx0UZ3U libc CodecvtNoConversion
_ZNKSt7codecvtIcc9_MbstatetE10do_unshiftERS0_PcS3_RS3_ iCWgjeqMHvg libc CodecvtUnshift: noconv, to_next = to
_ZNKSt7codecvtIcc9_MbstatetE9do_lengthERS0_PKcS4_m Xk7IZcfHDD8 libc CodecvtLength: min(from_end - from, max)
_ZNKSt7codecvtIcc9_MbstatetE2inERS0_PKcS4_RS4_PcS6_RS6_ TNexGlwiVEQ virtual do_in through the facet's vtable (CodecvtVtable::in, offset 0x38)
_ZNKSt7codecvtIcc9_MbstatetE3outERS0_PKcS4_RS4_PcS6_RS6_ 14xKj+SV72o virtual do_out (CodecvtVtable::out, offset 0x40)
_ZNKSt7codecvtIcc9_MbstatetE7unshiftERS0_PcS3_RS3_ N7z+Dnk1cS0 virtual do_unshift (CodecvtVtable::unshift, offset 0x48)
_ZNKSt7codecvtIcc9_MbstatetE6lengthERS0_PKcS4_m ikBt0lqkxPc virtual do_length (CodecvtVtable::length, offset 0x50)

The behaviour comes from [locale.codecvt.virtuals] (checked against the current draft, eel.is/c++draft/locale.codecvt.virtuals) and matches microsoft/STL e1ed302683 stl/inc/xlocale:

  • do_in and do_out: Table 93 gives noconv when "internT and externT are the same type, and input sequence is identical to converted sequence"; from_next = from, to_next = to.
  • do_unshift: Table 94 gives noconv when "no termination is needed for this state_type"; xlocale L738-L742 sets _Mid2 = _First2 and returns noconv.
  • do_always_noconv: "codecvt<char, char, mbstate_t> returns true."
  • do_length: for this specialization, "returns the lesser of max and (from_end - from)".

[locale.codecvt.members] makes each public member return its do_ member. For example in is "Returns: do_in(state, from, from_end, from_next, to, to_end, to_next)".

libc already holds these conversions. _ZNSt7codecvtIcc9_MbstatetE7_Getcat builds its C-locale facet on g_codecvtVtable in core/libs/prx/libc/src/LocaleSupport.cpp:

const GuestLocale::CodecvtVtable g_codecvtVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, CodecvtAlwaysNoconv, CodecvtConstantOne, CodecvtConstantOne, CodecvtNoConversion, CodecvtNoConversion, CodecvtUnshift, CodecvtLength};

The slot layout is pinned in GuestLocale.hpp by static_assert(offsetof(CodecvtVtable, alwaysNoconv) == 0x20), in == 0x38 and length == 0x50. Following #14373, which reaches libc's numpunct<char> helpers through a libc header instead of copying them, libc now returns that table from LibcCodecvtMethods_nid_no_patch(), declared in prx/libc/include/GuestLocale.hpp. The _nid_no_patch suffix keeps it out of the NID tables, so libc gets no new export and only libSceLibcInternal's exports change. Unlike #14373, no _nid_postfix symbol is added to libc: the 13.60 export inventory (ps5-1360-stubs) and its verified name map list these nine names only under libSceLibcInternal, and the inventory has no libc library at all, so nothing grounds them as libc.prx exports.

The new core/libs/prx/libSceLibcInternal/Codecvt.cpp holds the nine exports:

  • The do_ members call the matching slot of LibcCodecvtMethods_nid_no_patch(): CodecvtAlwaysNoconv, CodecvtNoConversion, CodecvtUnshift and CodecvtLength. They never read the facet's own vtable, so a derived facet that calls codecvt::do_in gets the base behaviour.
  • The public members call the facet's own vtable slot, so a title's derived facet runs its overrides. A null facet, or a facet with no vtable, makes them throw std::invalid_argument.

This PR also changes existing libc behaviour. On main, libc's CodecvtUnshift (the unshift slot of every codecvt<char, char> facet _Getcat builds) returned ok (0). It now returns noconv (3). [locale.codecvt.virtuals] Table 94 defines noconv as "no termination is needed for this state_type", which holds for the stateless char-to-char facet, and microsoft/STL e1ed302683 stl/inc/xlocale L738-L742 returns noconv from do_unshift. The test covers it: guest_libc_internal_codecvt checks do_unshift and unshift on the _Getcat facet return noconv, and the existing assertion in core/libs/tests/GuestLocale.cpp is updated from == 0 to == 3.

The standard length and do_length return int, but the vtable slot is typed std::size_t. Both therefore keep only the low 32 bits of the slot's result. This gives the right value when a guest override returns int in eax and leaves the upper half of rax undefined.

Changes:

  • New file core/libs/prx/libSceLibcInternal/Codecvt.cpp. The nine stubs are removed from Err.cpp. The entry points are declared in prx/libSceLibcInternal/include/Api.hpp.
  • core/libs/prx/libc/src/LocaleSupport.cpp: LibcCodecvtMethods_nid_no_patch() and the CodecvtUnshift result.
  • New guest test guest_libc_internal_codecvt (core/libs/tests/GuestLibcInternalCodecvt.cpp). It checks:
    • every do_ and public member on the facet _Getcat builds;
    • both orders of max against the range length, and an empty range;
    • that the state and the output buffer are left untouched;
    • that in, out, unshift and length reach an overriding facet's slots with their arguments and return its results, including length dropping the upper 32 bits of a do_length result;
    • that the do_ members called on that overriding facet still give the base results;
    • that the four public members throw std::invalid_argument for a null facet and for a facet with no vtable.
  • docs/dev/debt/libSceLibcInternal.md: the nine "prototype and behavior are unverified" lines are removed. A Functional entry is added for what the console was not checked for.

tools/progress.py collect_libraries():

  • origin/main (b11c7bf84f): 9921 done of 37146 (libSceLibcInternal 92 done, 1045 todo; libc 1628 done, 19 todo).
  • This branch: 9930 done of 37146 (libSceLibcInternal 101 done, 1036 todo; libc 1628 done, 19 todo).
  • Done goes up by 9: the nine libSceLibcInternal members. libc's counts do not change and no new todo appears.

Tested

Only the libraries and the focused test targets were built, on head 24e372834e (based on origin/main b11c7bf84f). No title was run.

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

The nine codecvt<char, char, _Mbstatet> exports threw. The do_ members
now run libc's C-locale conversion table, which libc returns from
LibcCodecvtMethods_nid_no_patch without adding any libc export, and in,
out, unshift and length call the facet's vtable slot as
[locale.codecvt.members] requires. libc's do_unshift now returns
noconv, which [locale.codecvt.virtuals] fixes for this specialization.

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

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

Progress report (b11c7bf → 24e3728)

📈 System libraries: 26.73% (+0.02%, +9 implemented, -9 removed functions)

✅ 9 implemented
Library Name
libSceLibcInternal sceLibcInternalCodecvtDoAlwaysNoconv_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtDoIn_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtDoLength_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtDoOut_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtDoUnshift_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtIn_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtLength_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtOut_nid_no_patch
libSceLibcInternal sceLibcInternalCodecvtUnshift_nid_no_patch
🗑️ 9 removed
Library Name
libSceLibcInternal sceLibcInternalPending3_027_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_028_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_029_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_030_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_031_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_032_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_033_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_034_nid_no_patch
libSceLibcInternal sceLibcInternalPending3_035_nid_no_patch

Changed libraries (implemented exports)

Library Before After Change
libSceLibcInternal 92/1137 (8.09%) 101/1137 (8.88%) +0.79%

@github-actions github-actions Bot added the open PR limit Closed over the open PR limit: 1 without merged PRs, 20 with at least 1, none from 20 label 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant