From c4238858fa75f3f8034f4e631d75093a482657b7 Mon Sep 17 00:00:00 2001 From: MihaZupan Date: Wed, 8 Jul 2026 23:27:32 +0200 Subject: [PATCH] Remove some branches in OptimizedInboxTextEncoder.GetIndexOfFirstCharToEncode --- .../Web/OptimizedInboxTextEncoder.cs | 67 ++++++++----------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/OptimizedInboxTextEncoder.cs b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/OptimizedInboxTextEncoder.cs index 8b4f62b65fab5d..aa31fae7b29522 100644 --- a/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/OptimizedInboxTextEncoder.cs +++ b/src/libraries/System.Text.Encodings.Web/src/System/Text/Encodings/Web/OptimizedInboxTextEncoder.cs @@ -394,61 +394,52 @@ public int GetIndexOfFirstByteToEncode(ReadOnlySpan data) public int GetIndexOfFirstCharToEncode(ReadOnlySpan data) { - int asciiCharsSkipped = 0; + _AssertThisNotNull(); // hoist "this != null" check out of hot loop below + + int idx = 0; #if NET // First, try calling the SIMD-enabled version. // The SIMD-enabled version handles only ASCII characters. if (data.Length >= 8) { - asciiCharsSkipped = data.IndexOfAnyExcept(_allowedAsciiChars); + idx = data.IndexOfAnyExcept(_allowedAsciiChars); - if ((uint)asciiCharsSkipped >= (uint)data.Length || char.IsAscii(data[asciiCharsSkipped])) + if ((uint)idx >= (uint)data.Length || char.IsAscii(data[idx])) { - // We either processed the whole span (in which case asciiCharsSkipped is -1), or we've found a disallowed ASCII char. - return asciiCharsSkipped; + // We either processed the whole span (in which case idx is -1), or we've found a disallowed ASCII char. + return idx; } + + data = data.Slice(idx); } #endif - int idx = asciiCharsSkipped; - - // If there's any leftover data, try consuming it now. - - if ((uint)idx < (uint)data.Length) + // unroll the loop 8x + while (data.Length >= 8) { - _AssertThisNotNull(); // hoist "this != null" check out of hot loop below - - // Slicing keeps the indexed accesses below provably in-bounds, so the JIT - // elides the bounds checks just as the previous pointer-based loop did. - ReadOnlySpan remaining = data.Slice(idx); - - // unroll the loop 8x - while (remaining.Length >= 8) - { - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[0])) { goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[1])) { idx += 1; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[2])) { idx += 2; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[3])) { idx += 3; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[4])) { idx += 4; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[5])) { idx += 5; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[6])) { idx += 6; goto Return; } - if (!_allowedBmpCodePoints.IsCharAllowed(remaining[7])) { idx += 7; goto Return; } - idx += 8; - remaining = remaining.Slice(8); - } + if (!_allowedBmpCodePoints.IsCharAllowed(data[0])) { goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[1])) { idx += 1; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[2])) { idx += 2; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[3])) { idx += 3; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[4])) { idx += 4; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[5])) { idx += 5; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[6])) { idx += 6; goto Return; } + if (!_allowedBmpCodePoints.IsCharAllowed(data[7])) { idx += 7; goto Return; } + idx += 8; + data = data.Slice(8); + } - foreach (char c in remaining) - { - if (!_allowedBmpCodePoints.IsCharAllowed(c)) { goto Return; } - idx++; - } + foreach (char c in data) + { + if (!_allowedBmpCodePoints.IsCharAllowed(c)) { goto Return; } + idx++; } - Return: + return -1; - Debug.Assert(0 <= idx && idx <= data.Length); - return (idx == data.Length) ? -1 : idx; + Return: + return idx; } ///