diff --git a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs index 1063849f601a..c392700c80f0 100644 --- a/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs +++ b/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; @@ -89,8 +88,6 @@ internal KestrelServerImpl( Features.Set(_serverAddresses); _transportManager = new TransportManager(_transportFactory, _multiplexedTransportFactory, ServiceContext); - - HttpCharacters.Initialize(); } private static ServiceContext CreateServiceContext(IOptions options, ILoggerFactory loggerFactory, DiagnosticSource? diagnosticSource) diff --git a/src/Shared/ServerInfrastructure/HttpCharacters.cs b/src/Shared/ServerInfrastructure/HttpCharacters.cs index 9892ed6fdbb7..e2ed13c11c82 100644 --- a/src/Shared/ServerInfrastructure/HttpCharacters.cs +++ b/src/Shared/ServerInfrastructure/HttpCharacters.cs @@ -1,148 +1,98 @@ // 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.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; namespace Microsoft.AspNetCore.Http; -internal static class HttpCharacters +[SkipLocalsInit] +internal static unsafe partial class HttpCharacters { - private const int _tableSize = 128; - private static readonly bool[] _alphaNumeric = InitializeAlphaNumeric(); - private static readonly bool[] _authority = InitializeAuthority(); - private static readonly bool[] _token = InitializeToken(); - private static readonly bool[] _host = InitializeHost(); - private static readonly bool[] _fieldValue = InitializeFieldValue(); + private static partial ReadOnlySpan LookupAuthority(); + private static partial ReadOnlySpan LookupToken(); + private static partial ReadOnlySpan LookupHost(); + private static partial ReadOnlySpan LookupFieldValue(); - internal static void Initialize() + private static partial Vector128 BitMaskLookupAuthority(); + private static partial Vector128 BitMaskLookupToken(); + private static partial Vector128 BitMaskLookupHost(); + private static partial Vector128 BitMaskLookupFieldValue(); + + public static bool ContainsInvalidAuthorityChar(ReadOnlySpan s) { - // Access _alphaNumeric to initialize static fields - var _ = _alphaNumeric; + var index = Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharVectorized(s, BitMaskLookupAuthority()) + : IndexOfInvalidCharScalar(s, LookupAuthority()); + + return index >= 0; } - private static bool[] InitializeAlphaNumeric() + public static int IndexOfInvalidHostChar(ReadOnlySpan s) { - // ALPHA and DIGIT https://tools.ietf.org/html/rfc5234#appendix-B.1 - var alphaNumeric = new bool[_tableSize]; - for (var c = '0'; c <= '9'; c++) - { - alphaNumeric[c] = true; - } - for (var c = 'A'; c <= 'Z'; c++) - { - alphaNumeric[c] = true; - } - for (var c = 'a'; c <= 'z'; c++) - { - alphaNumeric[c] = true; - } - return alphaNumeric; - } - - private static bool[] InitializeAuthority() - { - // Authority https://tools.ietf.org/html/rfc3986#section-3.2 - // Examples: - // microsoft.com - // hostname:8080 - // [::]:8080 - // [fe80::] - // 127.0.0.1 - // user@host.com - // user:password@host.com - var authority = new bool[_tableSize]; - Array.Copy(_alphaNumeric, authority, _tableSize); - authority[':'] = true; - authority['.'] = true; - authority['-'] = true; - authority['['] = true; - authority[']'] = true; - authority['@'] = true; - return authority; - } - - private static bool[] InitializeToken() - { - // tchar https://tools.ietf.org/html/rfc7230#appendix-B - var token = new bool[_tableSize]; - Array.Copy(_alphaNumeric, token, _tableSize); - token['!'] = true; - token['#'] = true; - token['$'] = true; - token['%'] = true; - token['&'] = true; - token['\''] = true; - token['*'] = true; - token['+'] = true; - token['-'] = true; - token['.'] = true; - token['^'] = true; - token['_'] = true; - token['`'] = true; - token['|'] = true; - token['~'] = true; - return token; - } - - private static bool[] InitializeHost() - { - // Matches Http.Sys - // Matches RFC 3986 except "*" / "+" / "," / ";" / "=" and "%" HEXDIG HEXDIG which are not allowed by Http.Sys - var host = new bool[_tableSize]; - Array.Copy(_alphaNumeric, host, _tableSize); - host['!'] = true; - host['$'] = true; - host['&'] = true; - host['\''] = true; - host['('] = true; - host[')'] = true; - host['-'] = true; - host['.'] = true; - host['_'] = true; - host['~'] = true; - return host; - } - - private static bool[] InitializeFieldValue() - { - // field-value https://tools.ietf.org/html/rfc7230#section-3.2 - var fieldValue = new bool[_tableSize]; - - fieldValue[0x9] = true; // HTAB - - for (var c = 0x20; c <= 0x7e; c++) // VCHAR and SP - { - fieldValue[c] = true; - } - return fieldValue; + return Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharVectorized(s, BitMaskLookupHost()) + : IndexOfInvalidCharScalar(s, LookupHost()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool ContainsInvalidAuthorityChar(Span s) + public static int IndexOfInvalidTokenChar(ReadOnlySpan s) + { + return Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharVectorized(s, BitMaskLookupToken()) + : IndexOfInvalidCharScalar(s, LookupToken()); + } + + public static int IndexOfInvalidTokenChar(ReadOnlySpan s) + { + return Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharVectorized(s, BitMaskLookupToken()) + : IndexOfInvalidCharScalar(s, LookupToken()); + } + + // Follows field-value rules in https://tools.ietf.org/html/rfc7230#section-3.2 + // Disallows characters > 0x7E. + public static int IndexOfInvalidFieldValueChar(ReadOnlySpan s) { - var authority = _authority; + return Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharVectorized(s, BitMaskLookupFieldValue()) + : IndexOfInvalidCharScalar(s, LookupFieldValue()); + } - for (var i = 0; i < s.Length; i++) + // Follows field-value rules for chars <= 0x7F. Allows extended characters > 0x7F. + public static int IndexOfInvalidFieldValueCharExtended(ReadOnlySpan s) + { + return Vector128.IsHardwareAccelerated && s.Length >= Vector128.Count + ? IndexOfInvalidCharExtendedVectorized(s, BitMaskLookupFieldValue()) + : IndexOfInvalidCharExtendedScalar(s, LookupFieldValue()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int IndexOfInvalidCharScalar(ReadOnlySpan value, ReadOnlySpan lookup) + { + for (var i = 0; i < value.Length; ++i) { - var c = s[i]; - if (c >= (uint)authority.Length || !authority[c]) + var b = value[i]; + + if (b >= lookup.Length || !lookup[b]) { - return true; + return i; } } - return false; + return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfInvalidHostChar(string s) + private static int IndexOfInvalidCharScalar(ReadOnlySpan value, ReadOnlySpan lookup) { - var host = _host; - - for (var i = 0; i < s.Length; i++) + for (var i = 0; i < value.Length; ++i) { - var c = s[i]; - if (c >= (uint)host.Length || !host[c]) + var c = value[i]; + + if (c >= lookup.Length || !lookup[c]) { return i; } @@ -152,14 +102,13 @@ public static int IndexOfInvalidHostChar(string s) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfInvalidTokenChar(string s) + private static int IndexOfInvalidCharExtendedScalar(ReadOnlySpan value, ReadOnlySpan lookup) { - var token = _token; - - for (var i = 0; i < s.Length; i++) + for (var i = 0; i < value.Length; ++i) { - var c = s[i]; - if (c >= (uint)token.Length || !token[c]) + var c = value[i]; + + if (c < (uint)lookup.Length && !lookup[c]) { return i; } @@ -168,57 +117,360 @@ public static int IndexOfInvalidTokenChar(string s) return -1; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfInvalidTokenChar(ReadOnlySpan span) + private static int IndexOfInvalidCharVectorized(ReadOnlySpan value, Vector128 bitMaskLookup) { - var token = _token; + Debug.Assert(Vector128.IsHardwareAccelerated); + Debug.Assert(value.Length >= Vector128.Count); - for (var i = 0; i < span.Length; i++) + // To check if a bit in a bitmask from the Bitmask is set, in a sequential code + // we would do ((1 << bitIndex) & bitmask) != 0 + // As there is no hardware instrinic for such a shift, we use a lookup that + // stores the shifted bitpositions. + // So (1 << bitIndex) becomes BitPosLook[bitIndex], which is simd-friendly. + // + // A bitmask from the Bitmask (see below) is created only for values 0..7 (one byte), + // so to avoid a explicit check for values outside 0..7, i.e. + // high nibbles 8..F, we use a bitpos that always results in escaping. + var bitPosLookup = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, // high-nibble 0..7 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // high-nibble 8..F + ).AsSByte(); + + var nibbleMaskSByte = Vector128.Create((sbyte)0xF); + var zeroMaskSByte = Vector128.Zero; + + var idx = (nuint)0; + var end = (nuint)(uint)(value.Length - Vector128.Count); + uint mask; + ref var ptr = ref MemoryMarshal.GetReference(value); + + while (idx <= end) { - var c = span[i]; - if (c >= (uint)token.Length || !token[c]) + var values = Vector128.LoadUnsafe(ref ptr, idx).AsSByte(); + + mask = CreateEscapingMask(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte); + if (mask != 0) { - return i; + goto Found; } + + idx += (uint)Vector128.Count; } + // Here we know that < 16 bytes are remaining. We shift the space around to process + // another full vector. + var remaining = (uint)value.Length - idx; + if ((nint)remaining > 0) + { + remaining -= (uint)Vector128.Count; + + var values = Vector128.LoadUnsafe(ref ptr, idx + remaining).AsSByte(); + + mask = CreateEscapingMask(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte); + if (mask != 0) + { + idx += remaining; + goto Found; + } + } + + goto NotFound; + + Found: + idx += GetIndexOfFirstNeedToEscape(mask); + return (int)idx; + + NotFound: return -1; } - // Follows field-value rules in https://tools.ietf.org/html/rfc7230#section-3.2 - // Disallows characters > 0x7E. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfInvalidFieldValueChar(string s) + private static int IndexOfInvalidCharVectorized(ReadOnlySpan value, Vector128 bitMaskLookup) { - var fieldValue = _fieldValue; + Debug.Assert(Vector128.IsHardwareAccelerated); + Debug.Assert(value.Length >= Vector128.Count); + + // See comment above for description. + var bitPosLookup = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, // high-nibble 0..7 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // high-nibble 8..F + ).AsSByte(); + + var nibbleMaskSByte = Vector128.Create((sbyte)0xF); + var zeroMaskSByte = Vector128.Zero; - for (var i = 0; i < s.Length; i++) + var idx = (nuint)0; + uint mask; + ref var ptr = ref Unsafe.As(ref MemoryMarshal.GetReference(value)); + + if (value.Length >= 2 * Vector128.Count) { - var c = s[i]; - if (c >= (uint)fieldValue.Length || !fieldValue[c]) + var end = (uint)(value.Length - 2 * Vector128.Count); + + do { - return i; + var source0 = Vector128.LoadUnsafe(ref ptr, idx); + var source1 = Vector128.LoadUnsafe(ref ptr, idx + 8); + var values = NarrowWithSaturation(source0, source1); + + mask = CreateEscapingMask(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte); + if (mask != 0) + { + goto Found; + } + + idx += 2 * (uint)Vector128.Count; + } + while (idx <= end); + } + + // Here we know that 8 to 15 chars are remaining. Process the first 8 chars. + if (idx <= (uint)(value.Length - Vector128.Count)) + { + var source = Vector128.LoadUnsafe(ref ptr, idx); + var values = NarrowWithSaturation(source, source); + + mask = CreateEscapingMask(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte); + if (mask != 0) + { + goto Found; + } + + idx += (uint)Vector128.Count; + } + + // Here we know that < 8 chars are remaining. We shift the space around to process + // another full vector. + var remaining = (uint)value.Length - idx; + if ((nint)remaining > 0) + { + remaining -= (uint)Vector128.Count; + + var source = Vector128.LoadUnsafe(ref ptr, idx + remaining); + var values = NarrowWithSaturation(source, source); + + mask = CreateEscapingMask(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte); + if (mask != 0) + { + idx += remaining; + goto Found; } } + goto NotFound; + + Found: + idx += GetIndexOfFirstNeedToEscape(mask); + return (int)idx; + + NotFound: return -1; } - // Follows field-value rules for chars <= 0x7F. Allows extended characters > 0x7F. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int IndexOfInvalidFieldValueCharExtended(string s) + private static int IndexOfInvalidCharExtendedVectorized(ReadOnlySpan value, Vector128 bitMaskLookup) { - var fieldValue = _fieldValue; + Debug.Assert(Vector128.IsHardwareAccelerated); + Debug.Assert(value.Length >= Vector128.Count); - for (var i = 0; i < s.Length; i++) + // See comment above for description. + var bitPosLookup = Vector128.Create( + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, // high-nibble 0..7 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // high-nibble 8..F + ).AsSByte(); + + var nibbleMaskSByte = Vector128.Create((sbyte)0xF); + var zeroMaskSByte = Vector128.Zero; + var nonAsciiMaskShort = Vector128.Create((ushort)0xFF80).AsInt16(); + + var idx = (nuint)0; + var end = (uint)(value.Length - Vector128.Count); + uint mask; + ref var ptr = ref Unsafe.As(ref MemoryMarshal.GetReference(value)); + + // Perf: don't unroll here, as it won't inline anymore due too excessive use of vector-registers. + while (idx <= end) { - var c = s[i]; - if (c < (uint)fieldValue.Length && !fieldValue[c]) + var source = Vector128.LoadUnsafe(ref ptr, idx); + var values = Vector128.Narrow(source, source); + var asciiMask = CreateAsciiMask(nonAsciiMaskShort, zeroMaskSByte, source); + + mask = CreateEscapingMaskExtended(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte, asciiMask); + if (mask != 0) { - return i; + goto Found; + } + + idx += (uint)Vector128.Count; + } + + // Here we know that < 8 chars are remaining. We shift the space around to process + // another full vector. + var remaining = (uint)value.Length - idx; + if ((nint)remaining > 0) + { + remaining -= (uint)Vector128.Count; + + var source = Vector128.LoadUnsafe(ref ptr, idx + remaining); + var values = Vector128.Narrow(source, source); + var asciiMask = CreateAsciiMask(nonAsciiMaskShort, zeroMaskSByte, source); + + mask = CreateEscapingMaskExtended(values, bitMaskLookup, bitPosLookup, nibbleMaskSByte, zeroMaskSByte, asciiMask); + if (mask != 0) + { + idx += remaining; + goto Found; } } + goto NotFound; + + Found: + idx += GetIndexOfFirstNeedToEscape(mask); + return (int)idx; + + NotFound: return -1; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static Vector128 CreateAsciiMask(Vector128 nonAsciiMask, Vector128 zero, Vector128 values) + { + var masked = values & nonAsciiMask; + var ascii = Vector128.Equals(masked, zero.AsInt16()); + return Vector128.Narrow(ascii, ascii); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint CreateEscapingMaskExtended( + Vector128 values, + Vector128 bitMaskLookup, + Vector128 bitPosLookup, + Vector128 nibbleMaskSByte, + Vector128 nullMaskSByte, + Vector128 asciiMask) + { + Debug.Assert(Vector128.IsHardwareAccelerated); + + var highNibbles = Vector128.ShiftRightLogical(values.AsInt32(), 4).AsSByte(); + var lowNibbles = values & nibbleMaskSByte; + + var bitMask = Shuffle(bitMaskLookup, lowNibbles); + var bitPositions = Shuffle(bitPosLookup, highNibbles); + + var mask = bitPositions & bitMask; + + var comparison = Vector128.Equals(mask, nullMaskSByte); + comparison = Vector128.Equals(comparison, nullMaskSByte); + comparison &= asciiMask; + + return comparison.ExtractMostSignificantBits(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetIndexOfFirstNeedToEscape(uint mask) + { + // Found at least one byte that needs to be escaped, figure out the index of + // the first one found that needs to be escaped within the 16 bytes. + Debug.Assert(mask > 0 && mask <= 65_535); + var tzc = uint.TrailingZeroCount(mask); + Debug.Assert(tzc < 16); + + return tzc; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint CreateEscapingMask( + Vector128 values, + Vector128 bitMaskLookup, + Vector128 bitPosLookup, + Vector128 nibbleMaskSByte, + Vector128 nullMaskSByte) + { + // To check if an input byte needs to be escaped or not, we use a bitmask-lookup. + // Therefore we split the input byte into the low- and high-nibble, which will get + // the row-/column-index in the bit-mask. + // The bitmask-lookup looks like: + // high-nibble + // low-nibble 0 1 2 3 4 5 6 7 8 9 A B C D E F + // 0 1 1 0 0 0 0 1 0 1 1 1 1 1 1 1 1 + // 1 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 2 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 3 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 4 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 5 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 6 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 7 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 8 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // 9 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // A 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // B 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 + // C 1 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 + // D 1 1 0 0 0 0 0 0 1 1 1 1 1 1 1 1 + // E 1 1 0 1 0 0 0 0 1 1 1 1 1 1 1 1 + // F 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 + // + // where 1 denotes the neeed for escaping, while 0 means no escaping needed. + // For high-nibbles in the range 8..F every input needs to be escaped, so we + // can omit them in the bit-mask, thus only high-nibbles in the range 0..7 need + // to be considered, hence the entries in the bit-mask can be of type byte. + // + // In the bitmask-lookup for each row (= low-nibble) a bit-mask for the + // high-nibbles (= columns) is created. + + Debug.Assert(Vector128.IsHardwareAccelerated); + + // Perf: the shift needs to be done as Int32, as there exists a hw-instruction and no sw-emulation needs to be done. + // Cf. https://github.com/dotnet/runtime/issues/75770 + var highNibbles = Vector128.ShiftRightLogical(values.AsInt32(), 4).AsSByte() & nibbleMaskSByte; + var lowNibbles = values & nibbleMaskSByte; + + var bitMask = Shuffle(bitMaskLookup, lowNibbles); + var bitPositions = Shuffle(bitPosLookup, highNibbles); + + var mask = bitPositions & bitMask; + + // There's no not-equal instruction, so we double compare to zero to achieve the same. + var comparison = Vector128.Equals(nullMaskSByte, mask); + comparison = Vector128.Equals(nullMaskSByte, comparison); + + return comparison.ExtractMostSignificantBits(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 NarrowWithSaturation(Vector128 v0, Vector128 v1) + { + Debug.Assert(Vector128.IsHardwareAccelerated); + + // TODO: https://github.com/dotnet/runtime/issues/75724 + + if (Sse2.IsSupported) + { + return Sse2.PackSignedSaturate(v0, v1); + } + else + { + // This is not the exact algorithm for saturation, but for the use-case + // here it's does what it should do. I.e. eliminate non-ASCII chars in the + // results. + + var v0HighNibbles = Vector128.ShiftRightLogical(v0, 8); + var v1HighNibbles = Vector128.ShiftRightLogical(v1, 8); + + var ascii0 = Vector128.Equals(Vector128.Zero, v0HighNibbles); + var ascii1 = Vector128.Equals(Vector128.Zero, v1HighNibbles); + + v0 &= ascii0; + v1 &= ascii1; + + return Vector128.Narrow(v0, v1); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector128 Shuffle(Vector128 vector, Vector128 indices) + { + // Perf: Ssse3.Shuffle produces better code + return Sse3.IsSupported + ? Ssse3.Shuffle(vector, indices) + : Vector128.Shuffle(vector, indices); } } diff --git a/src/Shared/ServerInfrastructure/HttpCharacters.generated.cs b/src/Shared/ServerInfrastructure/HttpCharacters.generated.cs new file mode 100644 index 000000000000..5b3f5396171a --- /dev/null +++ b/src/Shared/ServerInfrastructure/HttpCharacters.generated.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace Microsoft.AspNetCore.Http; + +[CompilerGenerated] +internal static partial class HttpCharacters +{ + private static class Constants + { + public static ReadOnlySpan LookupAuthority => new bool[] { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false }; + public static ReadOnlySpan LookupToken => new bool[] { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, true, true, true, false, false, true, true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, false, true, false }; + public static ReadOnlySpan LookupHost => new bool[] { false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, true, false, true, true, true, true, false, false, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false, true, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, true, false }; + public static ReadOnlySpan LookupFieldValue => new bool[] { false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false }; + } + + // Due to a JIT limitation we need to slice these constants (see comments above) in order + // to "unlink" the span, and allow proper hoisting out of the loop. + // This is tracked in https://github.com/dotnet/runtime/issues/12241 + + private static partial ReadOnlySpan LookupAuthority() => Constants.LookupAuthority.Slice(0); + private static partial ReadOnlySpan LookupToken() => Constants.LookupToken.Slice(0); + private static partial ReadOnlySpan LookupHost() => Constants.LookupHost.Slice(0); + private static partial ReadOnlySpan LookupFieldValue() => Constants.LookupFieldValue.Slice(0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static partial Vector128 BitMaskLookupAuthority() => Vector128.Create(0x47, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x8F, 0xAF, 0x8B, 0xAB, 0xAF).AsSByte(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static partial Vector128 BitMaskLookupToken() => Vector128.Create(0x17, 0x03, 0x07, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, 0x07, 0x0B, 0xAB, 0x2F, 0xAB, 0x0B, 0x8F).AsSByte(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static partial Vector128 BitMaskLookupHost() => Vector128.Create(0x57, 0x03, 0x07, 0x07, 0x03, 0x07, 0x03, 0x03, 0x03, 0x03, 0x0F, 0xAF, 0xAF, 0xAB, 0x2B, 0x8F).AsSByte(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static partial Vector128 BitMaskLookupFieldValue() => Vector128.Create(0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x83).AsSByte(); +}