From 3e851802487297bc336a04a0ca0ea7d423344046 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 20:24:15 +0500 Subject: [PATCH 1/7] Add SSE2 support --- Ramstack.Globbing/Internal/PathHelper.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Ramstack.Globbing/Internal/PathHelper.cs b/Ramstack.Globbing/Internal/PathHelper.cs index f318b41..219acaa 100644 --- a/Ramstack.Globbing/Internal/PathHelper.cs +++ b/Ramstack.Globbing/Internal/PathHelper.cs @@ -338,6 +338,23 @@ public PathSegmentIterator(int length) => if (_mask == 0) _position += Vector256.Count; } + else if (Sse2.IsSupported && !Avx2.IsSupported && _position + Vector128.Count <= _length) + { + var chunk = LoadVector128(ref source, _position); + var allowEscapingMask = CreateAllowEscaping128Bitmask(flags); + var slash = Vector128.Create((ushort)'/'); + var backslash = Vector128.Create((ushort)'\\'); + + var comparison = Sse2.Or( + Sse2.CompareEqual(chunk, slash), + Sse2.AndNot( + allowEscapingMask, + Sse2.CompareEqual(chunk, backslash))); + + _mask = (uint)Sse2.MoveMask(comparison.AsByte()); + if (_mask == 0) + _position += Vector128.Count; + } else { for (; _position < _length; _position++) From 960b7757c5810e73661dc8cd84bc10a13bab1ca7 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 20:24:35 +0500 Subject: [PATCH 2/7] Test SIMD fallbacks by disabling AVX2/SSE2 --- .github/workflows/test.yml | 40 +++++++++++++++++++ .../SimdConfigurationTests.cs | 17 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 Ramstack.Globbing.Tests/SimdConfigurationTests.cs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8e97837..ffbddde 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,3 +37,43 @@ jobs: - name: Test (Release) run: dotnet test -c Release --no-build + + - name: Test (Debug, Avx2=Disabled) + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + $env:COMPlus_EnableAVX2="0" + else + export COMPlus_EnableAVX2=0 + fi + dotnet test -c Debug --no-build + + - name: Test (Release, Avx2=Disabled) + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + $env:COMPlus_EnableAVX2="0" + else + export COMPlus_EnableAVX2=0 + fi + dotnet test -c Release --no-build + + - name: Test (Debug, Avx2=Disabled, Sse2=Disabled) + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + $env:COMPlus_EnableAVX2="0" + $env:COMPlus_EnableSSE2="0" + else + export COMPlus_EnableAVX2=0 + export COMPlus_EnableSSE2=0 + fi + dotnet test -c Debug --no-build + + - name: Test (Release, Avx2=Disabled, Sse2=Disabled) + run: | + if [[ "${{ runner.os }}" == "Windows" ]]; then + $env:COMPlus_EnableAVX2="0" + $env:COMPlus_EnableSSE2="0" + else + export COMPlus_EnableAVX2=0 + export COMPlus_EnableSSE2=0 + fi + dotnet test -c Release --no-build diff --git a/Ramstack.Globbing.Tests/SimdConfigurationTests.cs b/Ramstack.Globbing.Tests/SimdConfigurationTests.cs new file mode 100644 index 0000000..6ddb86a --- /dev/null +++ b/Ramstack.Globbing.Tests/SimdConfigurationTests.cs @@ -0,0 +1,17 @@ +using System.Runtime.Intrinsics.X86; + +namespace Ramstack.Globbing; + +[TestFixture] +public class SimdConfigurationTests +{ + [Test] + public void VerifySimdConfiguration() + { + var isAvx2Disabled = Environment.GetEnvironmentVariable("COMPlus_EnableAVX2") == "0"; + var isSse2Disabled = Environment.GetEnvironmentVariable("COMPlus_EnableSSE2") == "0"; + + Assert.That(isAvx2Disabled, Is.EqualTo(!Avx2.IsSupported)); + Assert.That(isSse2Disabled, Is.EqualTo(!Sse2.IsSupported)); + } +} From 06f268fd5be6da22a9fa5c9e0bc05df56832182c Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 20:31:30 +0500 Subject: [PATCH 3/7] Fix SIMD path handling --- Ramstack.Globbing/Internal/PathHelper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Ramstack.Globbing/Internal/PathHelper.cs b/Ramstack.Globbing/Internal/PathHelper.cs index 219acaa..4d46326 100644 --- a/Ramstack.Globbing/Internal/PathHelper.cs +++ b/Ramstack.Globbing/Internal/PathHelper.cs @@ -309,14 +309,16 @@ public PathSegmentIterator(int length) => while (_position < _length) { - if (Avx2.IsSupported && _mask != 0) + if ((Avx2.IsSupported || Sse2.IsSupported) && _mask != 0) { var offset = BitOperations.TrailingZeroCount(_mask); _last = _position + (nint)((uint)offset >> 1); _mask &= ~(3u << offset); if (_mask == 0) - _position += Vector256.Count; + _position += Avx2.IsSupported + ? Vector256.Count + : Vector128.Count; return ((int)start, (int)_last); } From 725ed870f37584f3e79588b2391889bec5f995cc Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 21:50:36 +0500 Subject: [PATCH 4/7] Update github workflow --- .github/workflows/test.yml | 68 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ffbddde..d8473d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,42 +38,54 @@ jobs: - name: Test (Release) run: dotnet test -c Release --no-build - - name: Test (Debug, Avx2=Disabled) + - name: Test (Debug, Avx2=Disabled, Windows) + if: runner.os == 'Windows' + run: + $env:COMPlus_EnableAVX2="0" + dotnet test -c Debug --no-build + + - name: Test (Release, Avx2=Disabled, Windows) + if: runner.os == 'Windows' + run: + $env:COMPlus_EnableAVX2="0" + dotnet test -c Release --no-build + + - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Windows) + if: runner.os == 'Windows' + run: + $env:COMPlus_EnableAVX2="0" + $env:COMPlus_EnableSSE2="0" + dotnet test -c Debug --no-build + + - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Windows) + if: runner.os == 'Windows' + run: + $env:COMPlus_EnableAVX2="0" + $env:COMPlus_EnableSSE2="0" + dotnet test -c Release --no-build + + - name: Test (Debug, Avx2=Disabled, Linux) + if: runner.os == 'Ubuntu' run: | - if [[ "${{ runner.os }}" == "Windows" ]]; then - $env:COMPlus_EnableAVX2="0" - else - export COMPlus_EnableAVX2=0 - fi + export COMPlus_EnableAVX2=0 dotnet test -c Debug --no-build - - name: Test (Release, Avx2=Disabled) + - name: Test (Release, Avx2=Disabled, Linux) + if: runner.os == 'Ubuntu' run: | - if [[ "${{ runner.os }}" == "Windows" ]]; then - $env:COMPlus_EnableAVX2="0" - else - export COMPlus_EnableAVX2=0 - fi + export COMPlus_EnableAVX2=0 dotnet test -c Release --no-build - - name: Test (Debug, Avx2=Disabled, Sse2=Disabled) + - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Linux) + if: runner.os == 'Ubuntu' run: | - if [[ "${{ runner.os }}" == "Windows" ]]; then - $env:COMPlus_EnableAVX2="0" - $env:COMPlus_EnableSSE2="0" - else - export COMPlus_EnableAVX2=0 - export COMPlus_EnableSSE2=0 - fi + export COMPlus_EnableAVX2=0 + export COMPlus_EnableSSE2=0 dotnet test -c Debug --no-build - - name: Test (Release, Avx2=Disabled, Sse2=Disabled) + - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Linux) + if: runner.os == 'Ubuntu' run: | - if [[ "${{ runner.os }}" == "Windows" ]]; then - $env:COMPlus_EnableAVX2="0" - $env:COMPlus_EnableSSE2="0" - else - export COMPlus_EnableAVX2=0 - export COMPlus_EnableSSE2=0 - fi + export COMPlus_EnableAVX2=0 + export COMPlus_EnableSSE2=0 dotnet test -c Release --no-build From a2c0b141ca29633b521e01a18d67edd60b72012e Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 21:54:39 +0500 Subject: [PATCH 5/7] Update github workflow --- .github/workflows/test.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8473d5..5d015bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,51 +40,51 @@ jobs: - name: Test (Debug, Avx2=Disabled, Windows) if: runner.os == 'Windows' - run: + run: | $env:COMPlus_EnableAVX2="0" dotnet test -c Debug --no-build - name: Test (Release, Avx2=Disabled, Windows) if: runner.os == 'Windows' - run: + run: | $env:COMPlus_EnableAVX2="0" dotnet test -c Release --no-build - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Windows) if: runner.os == 'Windows' - run: + run: | $env:COMPlus_EnableAVX2="0" $env:COMPlus_EnableSSE2="0" dotnet test -c Debug --no-build - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Windows) if: runner.os == 'Windows' - run: + run: | $env:COMPlus_EnableAVX2="0" $env:COMPlus_EnableSSE2="0" dotnet test -c Release --no-build - name: Test (Debug, Avx2=Disabled, Linux) - if: runner.os == 'Ubuntu' + if: runner.os != 'Windows' run: | export COMPlus_EnableAVX2=0 dotnet test -c Debug --no-build - name: Test (Release, Avx2=Disabled, Linux) - if: runner.os == 'Ubuntu' + if: runner.os != 'Windows' run: | export COMPlus_EnableAVX2=0 dotnet test -c Release --no-build - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Linux) - if: runner.os == 'Ubuntu' + if: runner.os != 'Windows' run: | export COMPlus_EnableAVX2=0 export COMPlus_EnableSSE2=0 dotnet test -c Debug --no-build - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Linux) - if: runner.os == 'Ubuntu' + if: runner.os != 'Windows' run: | export COMPlus_EnableAVX2=0 export COMPlus_EnableSSE2=0 From 2c2ca88daf48ec339c4adf6085899e82ae0bb8c5 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 22:02:18 +0500 Subject: [PATCH 6/7] Simplify github workflow --- .github/workflows/test.yml | 66 +++++++++++--------------------------- 1 file changed, 18 insertions(+), 48 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5d015bb..6b8dcdc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,54 +38,24 @@ jobs: - name: Test (Release) run: dotnet test -c Release --no-build - - name: Test (Debug, Avx2=Disabled, Windows) - if: runner.os == 'Windows' - run: | - $env:COMPlus_EnableAVX2="0" - dotnet test -c Debug --no-build - - - name: Test (Release, Avx2=Disabled, Windows) - if: runner.os == 'Windows' - run: | - $env:COMPlus_EnableAVX2="0" - dotnet test -c Release --no-build - - - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Windows) - if: runner.os == 'Windows' - run: | - $env:COMPlus_EnableAVX2="0" - $env:COMPlus_EnableSSE2="0" - dotnet test -c Debug --no-build - - - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Windows) - if: runner.os == 'Windows' - run: | - $env:COMPlus_EnableAVX2="0" - $env:COMPlus_EnableSSE2="0" - dotnet test -c Release --no-build - - - name: Test (Debug, Avx2=Disabled, Linux) - if: runner.os != 'Windows' - run: | - export COMPlus_EnableAVX2=0 - dotnet test -c Debug --no-build + - name: Test (Debug, Avx2=Disabled) + env: + COMPlus_EnableAVX2: "0" + run: dotnet test -c Debug --no-build - - name: Test (Release, Avx2=Disabled, Linux) - if: runner.os != 'Windows' - run: | - export COMPlus_EnableAVX2=0 - dotnet test -c Release --no-build + - name: Test (Release, Avx2=Disabled) + env: + COMPlus_EnableAVX2: "0" + run: dotnet test -c Release --no-build - - name: Test (Debug, Avx2=Disabled, Sse2=Disabled, Linux) - if: runner.os != 'Windows' - run: | - export COMPlus_EnableAVX2=0 - export COMPlus_EnableSSE2=0 - dotnet test -c Debug --no-build + - name: Test (Debug, Avx2=Disabled, Sse2=Disabled) + env: + COMPlus_EnableAVX2: "0" + COMPlus_EnableSSE2: "0" + run: dotnet test -c Debug --no-build - - name: Test (Release, Avx2=Disabled, Sse2=Disabled, Linux) - if: runner.os != 'Windows' - run: | - export COMPlus_EnableAVX2=0 - export COMPlus_EnableSSE2=0 - dotnet test -c Release --no-build + - name: Test (Release, Avx2=Disabled, Sse2=Disabled) + env: + COMPlus_EnableAVX2: "0" + COMPlus_EnableSSE2: "0" + run: dotnet test -c Release --no-build From 1cc30a2866d04e39a424b2530da0336e08978dbc Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 5 Jul 2025 22:30:37 +0500 Subject: [PATCH 7/7] Add comments for clarity --- Ramstack.Globbing/Internal/PathHelper.cs | 34 +++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/Ramstack.Globbing/Internal/PathHelper.cs b/Ramstack.Globbing/Internal/PathHelper.cs index 4d46326..d37baf3 100644 --- a/Ramstack.Globbing/Internal/PathHelper.cs +++ b/Ramstack.Globbing/Internal/PathHelper.cs @@ -305,6 +305,11 @@ public PathSegmentIterator(int length) => [MethodImpl(MethodImplOptions.AggressiveInlining)] public (int start, int final) GetNext(ref char source, MatchFlags flags) { + // + // Number of bits per char (ushort) in the MoveMask output + // + const uint BitsPerChar = 0b11; + var start = _last + 1; while (_position < _length) @@ -313,8 +318,15 @@ public PathSegmentIterator(int length) => { var offset = BitOperations.TrailingZeroCount(_mask); _last = _position + (nint)((uint)offset >> 1); - _mask &= ~(3u << offset); + // + // Clear the bits for the current separator to process the next position in the mask + // + _mask &= ~(BitsPerChar << offset); + + // + // Advance position to the next chunk when no separators remain in the mask + // if (_mask == 0) _position += Avx2.IsSupported ? Vector256.Count @@ -336,7 +348,17 @@ public PathSegmentIterator(int length) => allowEscapingMask, Avx2.CompareEqual(chunk, backslash))); + // + // Store the comparison bitmask and reuse it across iterations + // as long as it contains non-zero bits. + // This avoids reloading SIMD registers and repeating comparisons + // on the same chunk of data. + // _mask = (uint)Avx2.MoveMask(comparison.AsByte()); + + // + // Advance position to the next chunk when no separators found + // if (_mask == 0) _position += Vector256.Count; } @@ -353,7 +375,17 @@ public PathSegmentIterator(int length) => allowEscapingMask, Sse2.CompareEqual(chunk, backslash))); + // + // Store the comparison bitmask and reuse it across iterations + // as long as it contains non-zero bits. + // This avoids reloading SIMD registers and repeating comparisons + // on the same chunk of data. + // _mask = (uint)Sse2.MoveMask(comparison.AsByte()); + + // + // Advance position to the next chunk when no separators found + // if (_mask == 0) _position += Vector128.Count; }