Skip to content

Add ToUpperOrdinal and ToLowerOrdinal casing APIs#130140

Merged
tarekgh merged 12 commits into
dotnet:mainfrom
tarekgh:ordinal-casing-apis
Jul 9, 2026
Merged

Add ToUpperOrdinal and ToLowerOrdinal casing APIs#130140
tarekgh merged 12 commits into
dotnet:mainfrom
tarekgh:ordinal-casing-apis

Conversation

@tarekgh

@tarekgh tarekgh commented Jul 2, 2026

Copy link
Copy Markdown
Member

Implements the ordinal casing APIs approved in #90999.

APIs

  • char.ToUpperOrdinal(char) / char.ToLowerOrdinal(char)
  • Rune.ToUpperOrdinal(Rune) / Rune.ToLowerOrdinal(Rune)
  • string.ToUpperOrdinal() / string.ToLowerOrdinal()
  • MemoryExtensions.ToUpperOrdinal(ReadOnlySpan<char>, Span<char>) / ToLowerOrdinal(ReadOnlySpan<char>, Span<char>)

Notes

  • Ordinal casing uses the same simple, one-to-one mapping as OrdinalIgnoreCase comparisons. Two strings are OrdinalIgnoreCase-equal iff their ToUpperOrdinal results are ordinally equal.
  • Reuses the existing ordinal upper-casing tables and adds a mirrored lower-casing path. Six characters (U+0130, U+03F4, U+1E9E, U+2126, U+212A, U+212B) stay unchanged under lower casing to remain consistent with OrdinalIgnoreCase.
  • string.ToUpperOrdinal() / string.ToLowerOrdinal() return the same instance when an all-ASCII string is already in the requested case, avoiding an allocation.

Tests under System.Globalization.Tests validate the mappings against OrdinalIgnoreCase across the entire BMP.

Implement ordinal (simple, one-to-one) casing on Char, Rune, String, and MemoryExtensions, matching the mappings used by OrdinalIgnoreCase comparisons. String.ToUpperOrdinal/ToLowerOrdinal return the same instance when an all-ASCII string is already in the requested case.
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-globalization
See info in area-owners.md if you want to be subscribed.

Copilot AI 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.

Pull request overview

Adds new ordinal casing APIs across char, Rune, string, and span-based MemoryExtensions, implemented to match the StringComparison.OrdinalIgnoreCase folding behavior (one-to-one mapping, length-preserving), including a new ordinal lower-casing table path alongside the existing upper-casing tables.

Changes:

  • Introduces ToUpperOrdinal / ToLowerOrdinal APIs on char, Rune, string, and ReadOnlySpan<char> -> Span<char> transforms.
  • Implements ICU-backed ordinal lower-casing table initialization (native + managed), mirroring the existing upper-casing infrastructure and special-case handling.
  • Adds comprehensive System.Globalization.Tests coverage validating BMP-wide behavior and overload consistency.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/native/libs/System.Globalization.Native/System.Globalization.Native.def Exports the new native entrypoint for ordinal lower-casing page initialization.
src/native/libs/System.Globalization.Native/pal_common.c Implements GlobalizationNative_InitOrdinalLowerCasingPage using ICU simple lower casing + special cases.
src/native/libs/System.Globalization.Native/pal_casing.h Declares the new native PAL export for ordinal lower-casing page initialization.
src/native/libs/System.Globalization.Native/entrypoints.c Registers the new entrypoint for DllImport resolution.
src/libraries/Common/src/Interop/Interop.Casing.cs Adds managed LibraryImport for GlobalizationNative_InitOrdinalLowerCasingPage.
src/libraries/System.Private.CoreLib/src/System/Globalization/OrdinalCasing.Icu.cs Adds a mirrored lower-casing table + lazy per-page initialization and span-based lower casing.
src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs Adds TextInfo.ToLowerOrdinal(char) routing based on globalization mode (Invariant / NLS / ICU).
src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs Adds span-based Ordinal.ToLowerOrdinal entrypoint consistent with existing ToUpperOrdinal.
src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Globalization.cs Adds public span extension methods ToUpperOrdinal / ToLowerOrdinal.
src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Adds string.ToUpperOrdinal() / string.ToLowerOrdinal() with ASCII fast-path returning same instance when unchanged.
src/libraries/System.Private.CoreLib/src/System/Char.cs Adds public char.ToUpperOrdinal(char) / char.ToLowerOrdinal(char).
src/libraries/System.Private.CoreLib/src/System/Text/Rune.cs Adds public Rune.ToUpperOrdinal(Rune) / Rune.ToLowerOrdinal(Rune) implementations.
src/libraries/System.Runtime/ref/System.Runtime.cs Adds the new public APIs to the System.Runtime ref surface (char, string, Rune).
src/libraries/System.Memory/ref/System.Memory.cs Adds the new public APIs to the System.Memory ref surface (MemoryExtensions).
src/libraries/System.Runtime/tests/System.Globalization.Tests/System/Globalization/OrdinalCasingTests.cs New tests validating ordinal casing invariants and special-case behavior across the BMP and overloads.
src/libraries/System.Runtime/tests/System.Globalization.Tests/System.Globalization.Tests.csproj Includes the new test file in the test project.
src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp Adds the new native export for WASM/WASI callhelper pinvoke tables.
src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp Adds the new native export for WASM/browser callhelper pinvoke tables.

Comment thread src/native/libs/System.Globalization.Native/entrypoints.c Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 2, 2026 21:23
@tarekgh tarekgh added this to the 11.0.0 milestone Jul 2, 2026
@tarekgh

tarekgh commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

CC @lewing to advise if we need to do anything more for WASM with this change?

CC @jkotas & @GrabYourPitchforks for awareness.

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Tarek Mahmoud Sayed added 2 commits July 2, 2026 14:40
… modes

Invariant and NLS simple lowering could move characters such as the Kelvin, Ohm and Angstrom signs out of their ordinal upper-casing class, so ToLowerOrdinal stopped being consistent with OrdinalIgnoreCase in those modes. Keep the original character when its simple lower mapping would change its ordinal upper-casing form, and extend the invariant-mode test to cover ToLowerOrdinal across the BMP.
…ecutor

Guard the MICRO SIGN and GREEK SMALL FINAL SIGMA fold cases so they only run under ICU and invariant casing, since NLS does not treat them as OrdinalIgnoreCase-equal. Gate OrdinalIgnoreCase_Equivalence_HoldsInInvariantMode on RemoteExecutor.IsSupported so it is skipped on wasm and android instead of throwing PlatformNotSupportedException.
Copilot AI review requested due to automatic review settings July 6, 2026 19:40

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Comment thread src/libraries/System.Private.CoreLib/src/System/Globalization/Ordinal.cs Outdated
Comment thread src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp
Copilot AI review requested due to automatic review settings July 6, 2026 19:49

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Tarek Mahmoud Sayed added 2 commits July 6, 2026 13:46
Upper-case the source and lowered spans once each instead of calling
TextInfo.ToUpperOrdinal per non-ASCII character, which is expensive under NLS.
Copilot AI review requested due to automatic review settings July 6, 2026 21:09
Only a fixed set of five BMP scalars (Greek capital theta symbol, Latin
capital sharp S, Ohm, Kelvin and Angstrom signs) fall out of their ordinal
upper-casing class under simple invariant or NLS lowering. Scan the source
once with SearchValues and skip the pass entirely when none are present,
instead of upper-casing the whole span on every lowering.
Comment thread src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Outdated
Fast-path the leading ASCII run of the ICU ToUpperOrdinal/ToLowerOrdinal span
helpers with Ascii.ToUpper/ToLower, and replace the hand-rolled unsafe scan in
the string ChangeCaseOrdinal fast path with a vectorized IndexOfAnyExcept over
a SearchValues of the ASCII characters already in the requested case.
Copilot AI review requested due to automatic review settings July 7, 2026 20:00

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Comment thread src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Outdated
Comment thread src/libraries/System.Private.CoreLib/src/System/Char.cs
- Move ordinal-casing SearchValues into SearchValuesStorage using hardcoded
  ASCII literals so they are created lazily only when ordinal casing is used
- Use (uint) loop bounds in OrdinalCasing.Icu.cs to elide bounds checks
- Use collection expressions for single-char destination spans in tests
Copilot AI review requested due to automatic review settings July 8, 2026 18:17

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Comment thread src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs Outdated
Copilot AI review requested due to automatic review settings July 8, 2026 18:39

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

@tarekgh

tarekgh commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

/ba-g failures are WASm timeout or unrelated failures.

@tarekgh tarekgh merged commit a991d27 into dotnet:main Jul 9, 2026
162 of 172 checks passed
@Neme12

Neme12 commented Jul 10, 2026

Copy link
Copy Markdown

Yay 🎉

@dotnet-milestone-bot dotnet-milestone-bot Bot modified the milestones: 11.0.0, 11.0-preview7 Jul 10, 2026
lewing added a commit that referenced this pull request Jul 11, 2026
…tive (#130543)

## Summary

Fixes the browser-wasm **CoreCLR** library test leg
(`WasmTestOnChrome-CLR` → `System.Globalization.Extensions.Tests`),
failing on `main` since 2026-07-09 with:

```
System.DllNotFoundException : Unable to load shared library 'libSystem.Globalization.Native' ...
dynamic linking not enabled
   at Interop.Globalization.ToUnicode(...)
   at System.Globalization.IdnMapping.IcuTryGetUnicodeCore(...)
```

Tracked by #130479 (`blocking-clean-ci`).

## Root cause

#130140 added `GlobalizationNative_InitOrdinalLowerCasingPage` to the
generated `callhelpers-pinvoke.cpp` P/Invoke tables (browser + wasi) —
correctly inserting the sorted entry and its `extern "C"` declaration —
but did **not** update the hard-coded `EntryCount` in `s_PInvokeTables`
(left at `33`, now `34` entries).

`minipal_resolve_dllimport` (`src/native/minipal/entrypoints.h`)
linearly scans `entries[0 .. count-1]`. With `count = 33` over the
34-entry table, the **last** entry — `GlobalizationNative_ToUnicode` —
became unreachable, so its P/Invoke resolved to `NULL` and fell back to
`dlopen` (unsupported for CoreCLR-wasm) → `DllNotFoundException`. This
is why **only** `IdnMapping.GetUnicode`/`ToUnicode` tests failed while
`GetAscii` (`ToAscii`, index 32, still in range) kept passing.

The generator emits this count from the table length, so `34` is exactly
what a regeneration produces; #130140 hand-edited the generated file
without re-running it.

## Fix

Bump the `libSystem.Globalization.Native` `EntryCount` from `33` → `34`
in both:
- `src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp`
- `src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp`

All other module counts already match their tables (verified in both
files).

## Regression window (bisected on `main`, definition 129)

| Build | SHA | `LibraryTestsCoreCLR` |
|---|---|---|
| 1500867 (Jul 9 08:00) | `0f907bfa` (no #130140) | ✅ pass |
| 1502091 (Jul 9 20:00) | `6c584914` (has #130140) | ❌ fail — same
signature |

Emscripten 5.0.6/LLVM23 (Jun 25) and -O3 (#130111, Jul 2) were ruled out
— the leg stayed green with both for two weeks.

## Verification

- Native reproduction against the real `minipal/entrypoints.h`:
`ToUnicode` resolves `NULL` at count 33 and `FOUND` at count 34;
`ToAscii` resolves at 33 (matches the observed pass/fail split).
- The browser-wasm CoreCLR `WasmTestOnChrome-CLR` CI leg exercises this
end-to-end.

cc @tarekgh @pavelsavara @lewing

> [!NOTE]
> Root-cause investigation and this fix were produced with GitHub
Copilot.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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