Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/coreclr/vm/wasm/browser/callhelpers-pinvoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ extern "C" {
int32_t GlobalizationNative_IndexOf (void *, void *, int32_t, void *, int32_t, int32_t, void *);
void GlobalizationNative_InitICUFunctions (void *, void *, void *, void *);
void GlobalizationNative_InitOrdinalCasingPage (int32_t, void *);
void GlobalizationNative_InitOrdinalLowerCasingPage (int32_t, void *);
int32_t GlobalizationNative_IsNormalized (int32_t, void *, int32_t);
Comment thread
tarekgh marked this conversation as resolved.
int32_t GlobalizationNative_IsPredefinedLocale (void *);
int32_t GlobalizationNative_LastIndexOf (void *, void *, int32_t, void *, int32_t, int32_t, void *);
Expand Down Expand Up @@ -182,6 +183,7 @@ static const Entry s_libSystem_Globalization_Native [] = {
DllImportEntry(GlobalizationNative_IndexOf) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitICUFunctions) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitOrdinalCasingPage) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitOrdinalLowerCasingPage) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_IsNormalized) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_IsPredefinedLocale) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_LastIndexOf) // System.Private.CoreLib
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/wasm/wasi/callhelpers-pinvoke.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extern "C" {
int32_t GlobalizationNative_IndexOf (void *, void *, int32_t, void *, int32_t, int32_t, void *);
void GlobalizationNative_InitICUFunctions (void *, void *, void *, void *);
void GlobalizationNative_InitOrdinalCasingPage (int32_t, void *);
void GlobalizationNative_InitOrdinalLowerCasingPage (int32_t, void *);
int32_t GlobalizationNative_IsNormalized (int32_t, void *, int32_t);
int32_t GlobalizationNative_IsPredefinedLocale (void *);
int32_t GlobalizationNative_LastIndexOf (void *, void *, int32_t, void *, int32_t, int32_t, void *);
Expand Down Expand Up @@ -232,6 +233,7 @@ static const Entry s_libSystem_Globalization_Native [] = {
DllImportEntry(GlobalizationNative_IndexOf) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitICUFunctions) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitOrdinalCasingPage) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_InitOrdinalLowerCasingPage) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_IsNormalized) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_IsPredefinedLocale) // System.Private.CoreLib
DllImportEntry(GlobalizationNative_LastIndexOf) // System.Private.CoreLib
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Casing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,8 @@ internal static partial class Globalization

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_InitOrdinalCasingPage", StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial void InitOrdinalCasingPage(int pageNumber, char* pTarget);

[LibraryImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_InitOrdinalLowerCasingPage", StringMarshalling = StringMarshalling.Utf16)]
internal static unsafe partial void InitOrdinalLowerCasingPage(int pageNumber, char* pTarget);
}
}
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,10 @@ public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, Sy
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, T value, System.Collections.Generic.IEqualityComparer<T>? comparer = null) { throw null; }
public static int ToLower(this System.ReadOnlySpan<char> source, System.Span<char> destination, System.Globalization.CultureInfo? culture) { throw null; }
public static int ToLowerInvariant(this System.ReadOnlySpan<char> source, System.Span<char> destination) { throw null; }
public static int ToLowerOrdinal(this System.ReadOnlySpan<char> source, System.Span<char> destination) { throw null; }
public static int ToUpper(this System.ReadOnlySpan<char> source, System.Span<char> destination, System.Globalization.CultureInfo? culture) { throw null; }
public static int ToUpperInvariant(this System.ReadOnlySpan<char> source, System.Span<char> destination) { throw null; }
public static int ToUpperOrdinal(this System.ReadOnlySpan<char> source, System.Span<char> destination) { throw null; }
public static System.Memory<char> Trim(this System.Memory<char> memory) { throw null; }
public static System.ReadOnlyMemory<char> Trim(this System.ReadOnlyMemory<char> memory) { throw null; }
public static System.ReadOnlySpan<char> Trim(this System.ReadOnlySpan<char> span) { throw null; }
Expand Down
15 changes: 15 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Char.cs
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,14 @@ public static char ToUpper(char c)
// Converts a character to upper-case for invariant culture.
public static char ToUpperInvariant(char c) => TextInfo.ToUpperInvariant(c);

/// <summary>
/// Converts a character to uppercase using the casing rules used by
/// <see cref="StringComparison.OrdinalIgnoreCase"/> comparisons.
/// </summary>
/// <param name="c">The character to convert.</param>
/// <returns>The uppercase equivalent of <paramref name="c"/>.</returns>
public static char ToUpperOrdinal(char c) => TextInfo.ToUpperOrdinal(c);

/*===================================ToLower====================================
**
==============================================================================*/
Expand Down Expand Up @@ -558,6 +566,13 @@ public static char ToLower(char c)
// Converts a character to lower-case for invariant culture.
public static char ToLowerInvariant(char c) => TextInfo.ToLowerInvariant(c);

/// <summary>
/// Converts a character to lowercase using ordinal (simple, one-to-one) casing rules.
Comment thread
tarekgh marked this conversation as resolved.
/// </summary>
/// <param name="c">The character to convert.</param>
/// <returns>The lowercase equivalent of <paramref name="c"/>.</returns>
public static char ToLowerOrdinal(char c) => TextInfo.ToLowerOrdinal(c);

//
// IConvertible implementation
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -707,5 +708,63 @@ internal static int ToUpperOrdinal(ReadOnlySpan<char> source, Span<char> destina
OrdinalCasing.ToUpperOrdinal(source, destination);
return source.Length;
}

internal static int ToLowerOrdinal(ReadOnlySpan<char> source, Span<char> destination)
{
if (source.Overlaps(destination))
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_SpanOverlappedOperation);

// Assuming that changing case does not affect length
if (destination.Length < source.Length)
return -1;

if (GlobalizationMode.Invariant)
{
InvariantModeCasing.ToLower(source, destination);
PreserveOrdinalLowerCasingClass(source, destination);
return source.Length;
}

if (GlobalizationMode.UseNls)
{
TextInfo.Invariant.ChangeCaseToLower(source, destination); // this is the best so far for NLS.
PreserveOrdinalLowerCasingClass(source, destination);
return source.Length;
}

OrdinalCasing.ToLowerOrdinal(source, destination);
return source.Length;
}

// The only BMP scalars whose simple invariant/NLS lower mapping moves them out of their ordinal
// upper-casing class. Each one is its own ordinal upper-casing (ToUpperOrdinal(c) == c) yet lowers to a
// different letter, so simple lowering would break OrdinalIgnoreCase consistency for them. The ICU ordinal
// table already keeps them unchanged; invariant and NLS lowering do not, so they must be restored.
// U+03F4 GREEK CAPITAL THETA SYMBOL, U+1E9E LATIN CAPITAL LETTER SHARP S, U+2126 OHM SIGN,
// U+212A KELVIN SIGN, U+212B ANGSTROM SIGN. The ToLowerOrdinal_EveryBmpChar tests would fail if this set
// ever became incomplete.
private static readonly SearchValues<char> s_ordinalLowerCasingClassRestoreChars =
SearchValues.Create("\u03F4\u1E9E\u2126\u212A\u212B");

// Ordinal lower casing must never move a character out of its ordinal upper-casing class, otherwise it
// would stop being consistent with OrdinalIgnoreCase (for example the Kelvin, Ohm and Angstrom signs).
// Only the fixed set above is ever affected, so skip the pass entirely unless one of them is present.
private static void PreserveOrdinalLowerCasingClass(ReadOnlySpan<char> source, Span<char> destination)
{
int i = source.IndexOfAny(s_ordinalLowerCasingClassRestoreChars);
if (i < 0)
{
return;
}

// Restore each affected character to its original form; its ordinal lower casing is itself.
for (; i < source.Length; i++)
{
if (s_ordinalLowerCasingClassRestoreChars.Contains(source[i]))
{
destination[i] = source[i];
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,35 @@ internal static partial class OrdinalCasing
// Every cell is array of 512 character for uppercasing mapping.
private static readonly ushort[]?[] s_casingTable = InitCasingTable();

// s_lowerBasicLatin covers the lower casing for the Basic Latin & C0 Controls range.
// We don't lazily initialize this range because it is the most commonly used range and we'll cache it anyway very early.
private static readonly ushort[] s_lowerBasicLatin =
[
// Lower Casing

/* 0000-000f */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
/* 0010-001f */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
/* 0020-002f */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
/* 0030-003f */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
/* 0040-004f */ 0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
/* 0050-005f */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
/* 0060-006f */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
/* 0070-007f */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
/* 0080-008f */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
/* 0090-009f */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
/* 00a0-00af */ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
/* 00b0-00bf */ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
/* 00c0-00cf */ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
/* 00d0-00df */ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00d7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00df,
/* 00e0-00ef */ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
/* 00f0-00ff */ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff,
];

// s_lowerCasingTable mirrors s_casingTable but holds the simple lower casing mapping for the BMP.
// It is pre-seeded with the shared NoCasingPage for the permanently uncased pages (see InitLowerCasingTable);
// remaining pages are initialized lazily through ICU and identity pages collapse to the shared NoCasingPage.
private static readonly ushort[]?[] s_lowerCasingTable = InitLowerCasingTable();

/*
The table is initialized to:
{
Expand Down Expand Up @@ -147,7 +176,10 @@ internal static void ToUpperOrdinal(ReadOnlySpan<char> source, Span<char> destin
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(!GlobalizationMode.UseNls);

for (int i = 0; i < source.Length; i++)
// Quickly upper-case the leading run of ASCII characters, then handle the remainder.
Ascii.ToUpper(source, destination, out int start);

for (int i = start; (uint)i < (uint)source.Length; i++)
{
char c = source[i];
if (c <= '\u00FF') // optimize ASCII/Latin
Expand All @@ -172,6 +204,60 @@ internal static void ToUpperOrdinal(ReadOnlySpan<char> source, Span<char> destin
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static char ToLower(char c)
{
int pageNumber = ((int)c) >> 8;
if (pageNumber == 0) // optimize for ASCII range
{
return (char)s_lowerBasicLatin[(int)c];
}

ushort[]? casingTable = s_lowerCasingTable[pageNumber];

if (casingTable == NoCasingPage)
{
return c;
}

casingTable ??= InitOrdinalLowerCasingPage(pageNumber);

return (char)casingTable[((int)c) & 0xFF];
}

internal static void ToLowerOrdinal(ReadOnlySpan<char> source, Span<char> destination)
{
Debug.Assert(!GlobalizationMode.Invariant);
Debug.Assert(!GlobalizationMode.UseNls);

// Quickly lower-case the leading run of ASCII characters, then handle the remainder.
Ascii.ToLower(source, destination, out int start);

for (int i = start; (uint)i < (uint)source.Length; i++)
{
char c = source[i];
if (c <= '\u00FF') // optimize ASCII/Latin
Comment thread
tarekgh marked this conversation as resolved.
{
destination[i] = (char)s_lowerBasicLatin[c];
continue;
}

if (char.IsHighSurrogate(c) && i < source.Length - 1)
{
char cl = source[i + 1];
if (char.IsLowSurrogate(cl))
{
// well formed surrogates
SurrogateCasing.ToLower(c, cl, out destination[i], out destination[i + 1]);
i++; // skip the low surrogate
continue;
}
}

destination[i] = ToLower(c);
}
}

internal static int CompareStringIgnoreCase(ref char strA, int lengthA, ref char strB, int lengthB)
{
Debug.Assert(!GlobalizationMode.Invariant);
Expand Down Expand Up @@ -428,5 +514,46 @@ private static unsafe ushort[] InitOrdinalCasingPage(int pageNumber)
Volatile.Write(ref s_casingTable[pageNumber], casingTable);
return casingTable;
}

private static ushort[]?[] InitLowerCasingTable()
{
// Reuse the upper-casing NoCasing bitmap to pre-seed the lower-casing table. Every page that bitmap
// marks (the permanently uncased CJK/Hangul/PUA blocks) is also identity under lower casing, so seeding
// these pages with the shared NoCasingPage avoids first-use native calls without risking a wrong mapping.
ushort[]?[] table = new ushort[]?[s_casingTableInit.Length * 8];
for (int i = 0; i < s_casingTableInit.Length * 8; ++i)
{
// The bits are in reverse order
byte val = (byte)(s_casingTableInit[i / 8] >> (7 - (i % 8)));
if ((val & 1) == 1)
table[i] = NoCasingPage;
}
table[0] = s_lowerBasicLatin;
return table;
}

private static unsafe ushort[] InitOrdinalLowerCasingPage(int pageNumber)
{
Debug.Assert(pageNumber > 0 && pageNumber < 256);

ushort[] casingTable = new ushort[256];
fixed (ushort* table = casingTable)
{
char* pTable = (char*)table;
Interop.Globalization.InitOrdinalLowerCasingPage(pageNumber, pTable);
}

// If the page doesn't change any character, collapse it to the shared NoCasingPage to avoid
// retaining the 512-byte buffer. We still return the fully populated table for the current lookup.
int pageBase = pageNumber << 8;
int i = 0;
while (i < 256 && casingTable[i] == (ushort)(pageBase + i))
{
i++;
}

Volatile.Write(ref s_lowerCasingTable[pageNumber], i == 256 ? NoCasingPage : casingTable);
return casingTable;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,33 @@ internal static char ToUpperOrdinal(char c)
return OrdinalCasing.ToUpper(c);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static char ToLowerOrdinal(char c)
{
if (GlobalizationMode.Invariant)
{
return char.IsAscii(c)
? ToLowerAsciiInvariant(c)
: PreserveOrdinalLowerCasingClass(c, InvariantModeCasing.ToLower(c));
}

if (GlobalizationMode.UseNls)
{
return char.IsAscii(c)
? ToLowerAsciiInvariant(c)
: PreserveOrdinalLowerCasingClass(c, Invariant.ChangeCase(c, toUpper: false));
}

return OrdinalCasing.ToLower(c);
}

// Ordinal lower casing must never move a character out of its ordinal upper-casing class, otherwise it
// would stop being consistent with OrdinalIgnoreCase (for example the Kelvin, Ohm and Angstrom signs). The
// ICU ordinal table encodes this directly, but invariant and NLS simple lowering do not, so keep the original
// character whenever its simple lower mapping would change its ordinal upper-casing form.
private static char PreserveOrdinalLowerCasingClass(char c, char lower) =>
lower == c || ToUpperOrdinal(lower) == ToUpperOrdinal(c) ? lower : c;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ChangeCaseToLower(ReadOnlySpan<char> source, Span<char> destination)
{
Expand Down
Loading
Loading