< Summary

Information
Class: NanoRoute.Internals.ReadOnlyMemoryCharComparer
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Private/LowLevel/ReadOnlyMemoryCharComparer.cs
Line coverage
100%
Covered lines: 106
Uncovered lines: 0
Coverable lines: 106
Total lines: 218
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Private/LowLevel/ReadOnlyMemoryCharComparer.cs

#LineLine coverage
 1/********************************************************************************
 2* ReadOnlyMemoryCharComparer.cs                                                 *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Generic;
 8using System.Runtime.CompilerServices;
 9using System.Runtime.InteropServices;
 10
 11namespace NanoRoute.Internals
 12{
 13    internal sealed class ReadOnlyMemoryCharComparer : IEqualityComparer<ReadOnlyMemory<char>>
 14    {
 215        public static ReadOnlyMemoryCharComparer Instance { get; } = new();
 16
 17        public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y)
 218        {
 219            int length = x.Length;
 220            if (length != y.Length)
 121                return false;
 22
 223            ref char
 224                leftRef = ref MemoryMarshal.GetReference(x.Span),
 225                rightRef = ref MemoryMarshal.GetReference(y.Span);
 26
 227            int i = 0;
 28
 229            for (int bulkLength = length & -4; i < bulkLength; i += 4)
 230            {
 231                ulong
 232                    leftBlock = Unsafe.As<char, ulong>(ref Unsafe.Add(ref leftRef, i)),
 233                    rightBlock = Unsafe.As<char, ulong>(ref Unsafe.Add(ref rightRef, i));
 34
 235                if (BlockToUpper(leftBlock) != BlockToUpper(rightBlock))
 136                    return false;
 237            }
 38
 239            int remainingChars = length & 3;
 40
 241            switch (remainingChars)
 42            {
 43                case 3:
 144                    if (CharToUpper(Unsafe.Add(ref leftRef, i + 2)) != CharToUpper(Unsafe.Add(ref rightRef, i + 2)))
 145                        return false;
 146                    goto case 2;
 47                case 2:
 248                    if (CharToUpper(Unsafe.Add(ref leftRef, i + 1)) != CharToUpper(Unsafe.Add(ref rightRef, i + 1)))
 149                        return false;
 250                    goto case 1;
 51                case 1:
 252                    if (CharToUpper(Unsafe.Add(ref leftRef, i)) != CharToUpper(Unsafe.Add(ref rightRef, i)))
 153                        return false;
 254                    break;
 55            }
 56
 257            return true;
 58
 59            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 60            static char CharToUpper(char chr)
 61            {
 62                if ((chr & ~0x007Fu) is 0)
 63                {
 64                    uint
 65                        lowerIndicator = chr + 0x0080u - 0x0061u,
 66                        upperIndicator = chr + 0x0080u - 0x007Bu,
 67                        combinedIndicator = lowerIndicator ^ upperIndicator,
 68                        mask = (combinedIndicator & 0x0080u) >> 2;
 69
 70                    return (char) (chr ^ mask);
 71                }
 72
 73                return CharToUpperNonAscii(chr);
 74            }
 275        }
 76
 77        public int GetHashCode(ReadOnlyMemory<char> obj)
 278        {
 279            ref char inputRef = ref MemoryMarshal.GetReference(obj.Span);
 80
 81            unchecked
 282            {
 283                uint
 284                    p0 = 0xD6E8_FEB8u,
 285                    p1 = 0xA5A5_A5A5u;
 86
 287                int i = 0;
 88
 289                for (int bulkLength = obj.Length & -4; i < bulkLength; i += 4)
 290                {
 291                    ulong
 292                        block = Unsafe.As<char, ulong>(ref Unsafe.Add(ref inputRef, i)),
 293                        upperBlock = BlockToUpper(block);
 94
 95                    // Feed Marvin with the uppercased UTF-16 bytes, two chars at a time.
 296                    ref uint upperBlockRef = ref Unsafe.As<ulong, uint>(ref upperBlock);
 97
 298                    p0 += upperBlockRef;
 299                    MarvinBlock(ref p0, ref p1);
 100
 2101                    p0 += Unsafe.Add(ref upperBlockRef, 1);
 2102                    MarvinBlock(ref p0, ref p1);
 2103                }
 104
 2105                int remainingChars = obj.Length & 3;
 106
 2107                if (remainingChars is 0)
 1108                    p0 += 0x80u;
 109                else
 2110                {
 111                    // Pack the 1-3 char tail into a local block so BlockToUpper can handle it too.
 2112                    ulong tail = 0;
 113
 2114                    switch (remainingChars)
 115                    {
 116                        case 3:
 1117                            tail |= (ulong) Unsafe.Add(ref inputRef, i + 2) << 32;
 1118                            goto case 2;
 119                        case 2:
 2120                            tail |= (ulong) Unsafe.Add(ref inputRef, i + 1) << 16;
 2121                            goto case 1;
 122                        case 1:
 2123                            tail |= Unsafe.Add(ref inputRef, i);
 2124                            break;
 125                    }
 126
 2127                    tail = BlockToUpper(tail);
 128
 2129                    ref char tailRef = ref Unsafe.As<ulong, char>(ref tail);
 130                    // A 2- or 3-char tail has one complete Marvin block before padding.
 2131                    if (remainingChars >= 2)
 2132                    {
 2133                        p0 += Unsafe.As<char, uint>(ref tailRef);
 2134                        MarvinBlock(ref p0, ref p1);
 2135                    }
 136
 137                    // Add Marvin's final 0x80 padding after the remaining UTF-16 bytes.
 2138                    p0 += remainingChars is 2
 2139                        ? 0x80u
 2140                        : (uint) Unsafe.Add(ref tailRef, remainingChars - 1) | 0x0080_0000u;
 2141                }
 142
 2143                MarvinBlock(ref p0, ref p1);
 2144                MarvinBlock(ref p0, ref p1);
 145
 2146                return (int) (p1 ^ p0);
 147            }
 148
 149            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 150            static void MarvinBlock(ref uint p0, ref uint p1)
 151            {
 152                p1 ^= p0;
 153                p0 = RotateLeft(p0, 20);
 154                p0 += p1;
 155                p1 = RotateLeft(p1, 9);
 156                p1 ^= p0;
 157                p0 = RotateLeft(p0, 27);
 158                p0 += p1;
 159                p1 = RotateLeft(p1, 19);
 160
 161                [MethodImpl(MethodImplOptions.AggressiveInlining)]
 162                static uint RotateLeft(uint value, int offset) => value << offset | value >> (32 - offset);
 163            }
 2164        }
 165
 166        // https://github.com/dotnet/runtime/blob/ecc8cb5bc0411e0fb0549230f70dfe8ab302c65c/src/libraries/System.Private.
 167        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 168        private static ulong BlockToUpper(ulong input)
 2169        {
 2170            ulong
 2171                // Each 16-bit lane is non-zero here only when that char is outside ASCII.
 2172                nonAsciiMask = input & ~0x007F_007F_007F_007Ful,
 2173                lowerIndicator = input + 0x0080_0080_0080_0080ul - 0x0061_0061_0061_0061ul,
 2174                upperIndicator = input + 0x0080_0080_0080_0080ul - 0x007B_007B_007B_007Bul,
 2175                combinedIndicator = lowerIndicator ^ upperIndicator,
 2176                asciiUpper = input ^ ((combinedIndicator & 0x0080_0080_0080_0080ul) >> 2);
 177
 2178            if (nonAsciiMask is not 0)
 1179            {
 180                // ASCII lanes are already uppercased; only non-ASCII lanes need the slow path.
 1181                ref char inputRef = ref Unsafe.As<ulong, char>(ref input);
 1182                ref char resultRef = ref Unsafe.As<ulong, char>(ref asciiUpper);
 183
 1184                if ((nonAsciiMask & 0xFF80ul) is not 0)
 1185                    resultRef = CharToUpperNonAscii(inputRef);
 186
 1187                if ((nonAsciiMask & 0xFF80_0000ul) is not 0)
 1188                    Unsafe.Add(ref resultRef, 1) = CharToUpperNonAscii(Unsafe.Add(ref inputRef, 1));
 189
 1190                if ((nonAsciiMask & 0xFF80_0000_0000ul) is not 0)
 1191                    Unsafe.Add(ref resultRef, 2) = CharToUpperNonAscii(Unsafe.Add(ref inputRef, 2));
 192
 1193                if ((nonAsciiMask & 0xFF80_0000_0000_0000ul) is not 0)
 1194                    Unsafe.Add(ref resultRef, 3) = CharToUpperNonAscii(Unsafe.Add(ref inputRef, 3));
 1195            }
 196
 2197            return asciiUpper;
 2198        }
 199
 200        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 201        private static char CharToUpperNonAscii(char chr)
 1202        {
 1203            uint value = chr;
 204
 205            // UnicodeData.txt defines these Latin-1 letters as simple one-to-one
 206            // uppercase pairs. The lowercase ranges map by subtracting 0x20, while
 207            // the matching uppercase ranges are already normalized. The split ranges
 208            // intentionally skip multiplication and division signs.
 1209            if ((value - 0x00C0u) <= 0x0016u || (value - 0x00D8u) <= 0x0006u)
 1210                return chr;
 211
 1212            if ((value - 0x00E0u) <= 0x0016u || (value - 0x00F8u) <= 0x0006u)
 1213                return (char) (chr - 0x20);
 214
 1215            return char.ToUpperInvariant(chr);
 1216        }
 217    }
 218}