< 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: 98
Uncovered lines: 0
Coverable lines: 98
Total lines: 200
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

MethodBlocks covered Blocks not covered
ReadOnlyMemoryCharComparer()20
Equals(...)430
GetHashCode(...)380
BlockToUpper(...)240

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                // Slow...
 74                return char.ToUpperInvariant(chr);
 75            }
 276        }
 77
 78        public int GetHashCode(ReadOnlyMemory<char> obj)
 279        {
 280            ref char inputRef = ref MemoryMarshal.GetReference(obj.Span);
 81
 82            unchecked
 283            {
 284                uint
 285                    p0 = 0xD6E8_FEB8u,
 286                    p1 = 0xA5A5_A5A5u;
 87
 288                int i = 0;
 89
 290                for (int bulkLength = obj.Length & -4; i < bulkLength; i += 4)
 291                {
 292                    ulong
 293                        block = Unsafe.As<char, ulong>(ref Unsafe.Add(ref inputRef, i)),
 294                        upperBlock = BlockToUpper(block);
 95
 96                    // Feed Marvin with the uppercased UTF-16 bytes, two chars at a time.
 297                    ref uint upperBlockRef = ref Unsafe.As<ulong, uint>(ref upperBlock);
 98
 299                    p0 += upperBlockRef;
 2100                    MarvinBlock(ref p0, ref p1);
 101
 2102                    p0 += Unsafe.Add(ref upperBlockRef, 1);
 2103                    MarvinBlock(ref p0, ref p1);
 2104                }
 105
 2106                int remainingChars = obj.Length & 3;
 107
 2108                if (remainingChars is 0)
 1109                    p0 += 0x80u;
 110                else
 2111                {
 112                    // Pack the 1-3 char tail into a local block so BlockToUpper can handle it too.
 2113                    ulong tail = 0;
 114
 2115                    switch (remainingChars)
 116                    {
 117                        case 3:
 1118                            tail |= (ulong) Unsafe.Add(ref inputRef, i + 2) << 32;
 1119                            goto case 2;
 120                        case 2:
 2121                            tail |= (ulong) Unsafe.Add(ref inputRef, i + 1) << 16;
 2122                            goto case 1;
 123                        case 1:
 2124                            tail |= Unsafe.Add(ref inputRef, i);
 2125                            break;
 126                    }
 127
 2128                    tail = BlockToUpper(tail);
 129
 2130                    ref char tailRef = ref Unsafe.As<ulong, char>(ref tail);
 131                    // A 2- or 3-char tail has one complete Marvin block before padding.
 2132                    if (remainingChars >= 2)
 2133                    {
 2134                        p0 += Unsafe.As<char, uint>(ref tailRef);
 2135                        MarvinBlock(ref p0, ref p1);
 2136                    }
 137
 138                    // Add Marvin's final 0x80 padding after the remaining UTF-16 bytes.
 2139                    p0 += remainingChars is 2
 2140                        ? 0x80u
 2141                        : (uint) Unsafe.Add(ref tailRef, remainingChars - 1) | 0x0080_0000u;
 2142                }
 143
 2144                MarvinBlock(ref p0, ref p1);
 2145                MarvinBlock(ref p0, ref p1);
 146
 2147                return (int) (p1 ^ p0);
 148            }
 149
 150            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 151            static void MarvinBlock(ref uint p0, ref uint p1)
 152            {
 153                p1 ^= p0;
 154                p0 = RotateLeft(p0, 20);
 155                p0 += p1;
 156                p1 = RotateLeft(p1, 9);
 157                p1 ^= p0;
 158                p0 = RotateLeft(p0, 27);
 159                p0 += p1;
 160                p1 = RotateLeft(p1, 19);
 161
 162                [MethodImpl(MethodImplOptions.AggressiveInlining)]
 163                static uint RotateLeft(uint value, int offset) => value << offset | value >> (32 - offset);
 164            }
 2165        }
 166
 167        // https://github.com/dotnet/runtime/blob/ecc8cb5bc0411e0fb0549230f70dfe8ab302c65c/src/libraries/System.Private.
 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 = char.ToUpperInvariant(inputRef);
 186
 1187                if ((nonAsciiMask & 0xFF80_0000ul) is not 0)
 1188                    Unsafe.Add(ref resultRef, 1) = char.ToUpperInvariant(Unsafe.Add(ref inputRef, 1));
 189
 1190                if ((nonAsciiMask & 0xFF80_0000_0000ul) is not 0)
 1191                    Unsafe.Add(ref resultRef, 2) = char.ToUpperInvariant(Unsafe.Add(ref inputRef, 2));
 192
 1193                if ((nonAsciiMask & 0xFF80_0000_0000_0000ul) is not 0)
 1194                    Unsafe.Add(ref resultRef, 3) = char.ToUpperInvariant(Unsafe.Add(ref inputRef, 3));
 1195            }
 196
 2197            return asciiUpper;
 2198        }
 199    }
 200}