File: src\runtime\src\libraries\System.Private.CoreLib\src\System\Globalization\OrdinalCasing.Icu.cs
Web Access
Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace System.Globalization
{
    internal static partial class OrdinalCasing
    {
        // NoCasingPage means the Unicode page doesn't support any casing and no case translation is needed.
        private static ushort[] NoCasingPage => [];

        // s_basicLatin is covering the casing for the Basic Latin & C0 Controls range.
        // we are not lazy initializing this range because it is the most common used range and we'll cache it anyway very early.
        private static readonly ushort[] s_basicLatin =
        [
            // Upper 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, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
            /* 0050-005f */  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
            /* 0060-006f */  0x0060, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
            /* 0070-007f */  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 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, 0x039c, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
            /* 00c0-00cf */  0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
            /* 00d0-00df */  0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
            /* 00e0-00ef */  0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
            /* 00f0-00ff */  0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00f7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x0178,
        ];

        // s_casingTable is covering the Unicode BMP plane only. Surrogate casing is handled separately.
        // Every cell in the table is covering the casing of 256 characters in the BMP.
        // 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:
        {
            // 0000-07FF //       s_basicLatin,            null,            null,            null,            null,            null,            null,            null,
            // 0800-0FFF //               null,            null,            null,            null,            null,            null,            null,            null,
            // 1000-17FF //               null,    NoCasingPage,            null,            null,    NoCasingPage,    NoCasingPage,            null,            null,
            // 1800-1FFF //               null,            null,            null,            null,            null,            null,            null,            null,
            // 2000-27FF //               null,            null,    NoCasingPage,    NoCasingPage,            null,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 2800-2FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,            null,            null,            null,            null,            null,
            // 3000-37FF //               null,            null,            null,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 3800-3FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 4000-47FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 4800-4FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 5000-57FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 5800-5FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 6000-67FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 6800-6FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 7000-77FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 7800-7FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 8000-87FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 8800-8FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 9000-97FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // 9800-9FFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,            null,
            // A000-A7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,            null,    NoCasingPage,            null,            null,
            // A800-AFFF //               null,            null,            null,            null,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // B000-B7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // B800-BFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // C000-C7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // C800-CFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // D000-D7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,            null,
            // D800-DFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // E000-E7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // E800-EFFF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // F000-F7FF //       NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,    NoCasingPage,
            // F800-FFFF //       NoCasingPage,    NoCasingPage,            null,            null,    NoCasingPage,            null,            null,            null,
        };
*/

        // 0 - null
        // 1 - NoCasingPage
        // The bits are in reverse order for readability, i.e. the highest order bit refers to
        // the lowest index.
        private static ReadOnlySpan<byte> s_casingTableInit => // 32
        [
            /* 0000-07FF */    0b00000000,
            /* 0800-0FFF */    0b00000000,
            /* 1000-17FF */    0b01001100,
            /* 1800-1FFF */    0b00000000,
            /* 2000-27FF */    0b00110111,
            /* 2800-2FFF */    0b11100000,
            /* 3000-37FF */    0b00011111,
            /* 3800-3FFF */    0b11111111,
            /* 4000-47FF */    0b11111111,
            /* 4800-4FFF */    0b11111111,
            /* 5000-57FF */    0b11111111,
            /* 5800-5FFF */    0b11111111,
            /* 6000-67FF */    0b11111111,
            /* 6800-6FFF */    0b11111111,
            /* 7000-77FF */    0b11111111,
            /* 7800-7FFF */    0b11111111,
            /* 8000-87FF */    0b11111111,
            /* 8800-8FFF */    0b11111111,
            /* 9000-97FF */    0b11111111,
            /* 9800-9FFF */    0b11111110,
            /* A000-A7FF */    0b11110100,
            /* A800-AFFF */    0b00001111,
            /* B000-B7FF */    0b11111111,
            /* B800-BFFF */    0b11111111,
            /* C000-C7FF */    0b11111111,
            /* C800-CFFF */    0b11111111,
            /* D000-D7FF */    0b11111110,
            /* D800-DFFF */    0b11111111,
            /* E000-E7FF */    0b11111111,
            /* E800-EFFF */    0b11111111,
            /* F000-F7FF */    0b11111111,
            /* F800-FFFF */    0b11001000,
        ];

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

            ushort[]? casingTable = s_casingTable[pageNumber];

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

            casingTable ??= InitOrdinalCasingPage(pageNumber);

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

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

            // 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
                {
                    destination[i] = (char)s_basicLatin[c];
                    continue;
                }

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

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

        [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
                {
                    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);
            Debug.Assert(!GlobalizationMode.UseNls);

            int length = Math.Min(lengthA, lengthB);

            ref char charA = ref strA;
            ref char charB = ref strB;

            int index = 0;

            while (index < length)
            {
                char a = charA;
                char b = charB;
                char lowSurrogateA = '\0';

                if (!char.IsHighSurrogate(a) || index >= lengthA - 1 || !char.IsLowSurrogate(lowSurrogateA = Unsafe.Add(ref charA, 1)))
                {
                    if (!char.IsHighSurrogate(b) || index >= lengthB - 1 || !char.IsLowSurrogate(Unsafe.Add(ref charB, 1)))
                    {
                        //
                        // Neither A or B are surrogates
                        //

                        if (b == a)
                        {
                            index++;
                            charA = ref Unsafe.Add(ref charA, 1);
                            charB = ref Unsafe.Add(ref charB, 1);
                            continue;
                        }

                        char aUpper = ToUpper(a);
                        char bUpper = ToUpper(b);

                        if (aUpper == bUpper)
                        {
                            index++;
                            charA = ref Unsafe.Add(ref charA, 1);
                            charB = ref Unsafe.Add(ref charB, 1);
                            continue;
                        }

                        return aUpper - bUpper;
                    }

                    //
                    // charA is not surrogate and charB is valid surrogate
                    //

                    return -1;
                }

                //
                // A is Surrogate
                //

                char lowSurrogateB = '\0';

                if (!char.IsHighSurrogate(b) || index >= lengthB - 1 || !char.IsLowSurrogate(lowSurrogateB = Unsafe.Add(ref charB, 1)))
                {
                    //
                    // charB is not surrogate and charA is surrogate
                    //

                    return 1;
                }

                //
                // charA and charB are surrogates
                //

                Debug.Assert(lowSurrogateA != '\0');
                Debug.Assert(lowSurrogateB != '\0');

                if (a == b && lowSurrogateA == lowSurrogateB)
                {
                    index += 2;
                    charA = ref Unsafe.Add(ref charA, 2);
                    charB = ref Unsafe.Add(ref charB, 2);
                    continue;
                }

                uint upperSurrogateA = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(a, lowSurrogateA));
                uint upperSurrogateB = CharUnicodeInfo.ToUpper(UnicodeUtility.GetScalarFromUtf16SurrogatePair(b, lowSurrogateB));

                if (upperSurrogateA == upperSurrogateB)
                {
                    index += 2;
                    charA = ref Unsafe.Add(ref charA, 2);
                    charB = ref Unsafe.Add(ref charB, 2);
                    continue;
                }

                return (int)upperSurrogateA - (int)upperSurrogateB;
            }

            return lengthA - lengthB;
        }

        internal static unsafe int IndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
        {
            Debug.Assert(value.Length > 0);
            Debug.Assert(value.Length <= source.Length);

            Debug.Assert(!GlobalizationMode.Invariant);
            Debug.Assert(!GlobalizationMode.UseNls);

            fixed (char* pSource = &MemoryMarshal.GetReference(source))
            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
            {
                char* pSourceLimit = pSource + (source.Length - value.Length);
                char* pValueLimit = pValue + value.Length - 1;
                char* pCurrentSource = pSource;

                while (pCurrentSource <= pSourceLimit)
                {
                    char *pVal = pValue;
                    char *pSrc = pCurrentSource;

                    while (pVal <= pValueLimit)
                    {
                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
                        {
                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
                                break; // no match

                            pVal++;
                            pSrc++;
                            continue;
                        }

                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1)))
                        {
                            // Well formed surrogates
                            // both the source and the Value have well-formed surrogates.
                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
                                break; // no match

                            pSrc += 2;
                            pVal += 2;
                            continue;
                        }

                        if (*pVal != *pSrc)
                            break; // no match

                        pSrc++;
                        pVal++;
                    }

                    if (pVal > pValueLimit)
                    {
                        // Found match.
                        return (int) (pCurrentSource - pSource);
                    }

                    pCurrentSource++;
                }

                return -1;
            }
        }

        internal static unsafe int LastIndexOf(ReadOnlySpan<char> source, ReadOnlySpan<char> value)
        {
            Debug.Assert(value.Length > 0);
            Debug.Assert(value.Length <= source.Length);

            Debug.Assert(!GlobalizationMode.Invariant);
            Debug.Assert(!GlobalizationMode.UseNls);

            fixed (char* pSource = &MemoryMarshal.GetReference(source))
            fixed (char* pValue  = &MemoryMarshal.GetReference(value))
            {
                char* pValueLimit = pValue + value.Length - 1;
                char* pCurrentSource = pSource + (source.Length - value.Length);

                while (pCurrentSource >= pSource)
                {
                    char *pVal = pValue;
                    char *pSrc = pCurrentSource;

                    while (pVal <= pValueLimit)
                    {
                        if (!char.IsHighSurrogate(*pVal) || pVal == pValueLimit)
                        {
                            if (*pVal != *pSrc && ToUpper(*pVal) != ToUpper(*pSrc))
                                break; // no match

                            pVal++;
                            pSrc++;
                            continue;
                        }

                        if (char.IsHighSurrogate(*pSrc) && char.IsLowSurrogate(*(pSrc + 1)) && char.IsLowSurrogate(*(pVal + 1)))
                        {
                            // Well formed surrogates
                            // both the source and the Value have well-formed surrogates.
                            if (!SurrogateCasing.Equal(*pSrc, *(pSrc + 1), *pVal, *(pVal + 1)))
                                break; // no match

                            pSrc += 2;
                            pVal += 2;
                            continue;
                        }

                        if (*pVal != *pSrc)
                            break; // no match

                        pSrc++;
                        pVal++;
                    }

                    if (pVal > pValueLimit)
                    {
                        // Found match.
                        return (int)(pCurrentSource - pSource);
                    }

                    pCurrentSource--;
                }

                return -1;
            }
        }

        private static ushort[]?[] InitCasingTable()
        {
            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_basicLatin;
            return table;
        }

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

            ushort[] casingTable = new ushort[256];
            fixed (ushort* table = casingTable)
            {
                char* pTable = (char*)table;
                Interop.Globalization.InitOrdinalCasingPage(pageNumber, pTable);
            }
            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;
        }
    }
}