Skip to content

Commit 6ab683b

Browse files
committed
Use it in System.Collections.Immutable's KeyAnalyzer
1 parent 49f4573 commit 6ab683b

File tree

6 files changed

+38
-73
lines changed

6 files changed

+38
-73
lines changed
Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Runtime.CompilerServices;
5+
46
namespace System.Text
57
{
68
/// <summary>Provides downlevel polyfills for Ascii helper APIs.</summary>
@@ -11,17 +13,39 @@ public static bool IsValid(string value)
1113
return IsValid(value.AsSpan());
1214
}
1315

14-
public static bool IsValid(ReadOnlySpan<char> value)
16+
public static unsafe bool IsValid(ReadOnlySpan<char> value)
1517
{
16-
foreach (char c in value)
18+
fixed (char* src = value)
1719
{
18-
if (c > 127)
20+
uint* ptrUInt32 = (uint*)src;
21+
int length = value.Length;
22+
23+
while (length >= 4)
1924
{
20-
return false;
25+
if (!AllCharsInUInt32AreAscii(ptrUInt32[0] | ptrUInt32[1]))
26+
{
27+
return false;
28+
}
29+
30+
ptrUInt32 += 2;
31+
length -= 4;
32+
}
33+
34+
char* ptrChar = (char*)ptrUInt32;
35+
while (length-- > 0)
36+
{
37+
char ch = *ptrChar++;
38+
if (ch >= 0x80)
39+
{
40+
return false;
41+
}
2142
}
2243
}
2344

2445
return true;
46+
47+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48+
static bool AllCharsInUInt32AreAscii(uint value) => (value & ~0x007F_007Fu) == 0;
2549
}
2650
}
2751
}

src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<PackageDescription>This package provides collections that are thread safe and guaranteed to never change their contents, also known as immutable collections. Like strings, any methods that perform modifications will not change the existing instance but instead return a new instance. For efficiency reasons, the implementation uses a sharing mechanism to ensure that newly created instances share as much data as possible with the previous instance while ensuring that operations have a predictable time complexity.
99

1010
The System.Collections.Immutable library is built-in as part of the shared framework in .NET Runtime. The package can be installed when you need to use it in other target frameworks.</PackageDescription>
11+
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
1112
</PropertyGroup>
1213

1314
<ItemGroup>

src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/Hashing.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.Numerics;
77
using System.Runtime.InteropServices;
8+
using System.Text;
89

910
namespace System.Collections.Frozen
1011
{
@@ -77,7 +78,7 @@ public static unsafe int GetHashCodeOrdinal(ReadOnlySpan<char> s)
7778
// useful if the string only contains ASCII characters
7879
public static unsafe int GetHashCodeOrdinalIgnoreCaseAscii(ReadOnlySpan<char> s)
7980
{
80-
Debug.Assert(KeyAnalyzer.IsAllAscii(s));
81+
Debug.Assert(Ascii.IsValid(s));
8182

8283
// We "normalize to lowercase" every char by ORing with 0x20. This casts
8384
// a very wide net because it will change, e.g., '^' to '~'. But that should

src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/String/KeyAnalyzer.cs

Lines changed: 6 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
using System.Buffers;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
#if !NET
8-
using System.Runtime.CompilerServices;
9-
#endif
7+
using System.Text;
108

119
namespace System.Collections.Frozen
1210
{
@@ -145,7 +143,7 @@ private static AnalysisResults CreateAnalysisResults(
145143
// Get a span representing the slice of the uniqueString which will be hashed.
146144
// If the slice isn't ASCII, bail out to return the results.
147145
if (!allUniqueStringsAreConfirmedAscii &&
148-
!IsAllAscii(getHashString(uniqueString, index, count)))
146+
!Ascii.IsValid(getHashString(uniqueString, index, count)))
149147
{
150148
allAsciiIfIgnoreCase = false;
151149
canSwitchIgnoreCaseHashToCaseSensitive = false;
@@ -161,7 +159,7 @@ private static AnalysisResults CreateAnalysisResults(
161159
// and as we have just checked that IsAllAscii(hashString) is true
162160
// then we know IsAllAscii(uniqueString) must be true,
163161
// so we can skip the check.
164-
if ((count > 0 && !allUniqueStringsAreConfirmedAscii && !IsAllAscii(uniqueString.AsSpan())) ||
162+
if ((count > 0 && !allUniqueStringsAreConfirmedAscii && !Ascii.IsValid(uniqueString)) ||
165163
ContainsAnyAsciiLetters(uniqueString.AsSpan()))
166164
{
167165
canSwitchIgnoreCaseHashToCaseSensitive = false;
@@ -190,7 +188,7 @@ private static bool AreAllAscii(ReadOnlySpan<string> strings)
190188
{
191189
foreach (string s in strings)
192190
{
193-
if (!IsAllAscii(s.AsSpan()))
191+
if (!Ascii.IsValid(s))
194192
{
195193
return false;
196194
}
@@ -199,65 +197,13 @@ private static bool AreAllAscii(ReadOnlySpan<string> strings)
199197
return true;
200198
}
201199

202-
internal static unsafe bool IsAllAscii(ReadOnlySpan<char> s)
203-
{
204-
#if NET
205-
return System.Text.Ascii.IsValid(s);
206-
#else
207-
fixed (char* src = s)
208-
{
209-
uint* ptrUInt32 = (uint*)src;
210-
int length = s.Length;
211-
212-
while (length >= 4)
213-
{
214-
if (!AllCharsInUInt32AreAscii(ptrUInt32[0] | ptrUInt32[1]))
215-
{
216-
return false;
217-
}
218-
219-
ptrUInt32 += 2;
220-
length -= 4;
221-
}
222-
223-
char* ptrChar = (char*)ptrUInt32;
224-
while (length-- > 0)
225-
{
226-
char ch = *ptrChar++;
227-
if (ch >= 0x80)
228-
{
229-
return false;
230-
}
231-
}
232-
}
233-
234-
return true;
235-
236-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
237-
static bool AllCharsInUInt32AreAscii(uint value) => (value & ~0x007F_007Fu) == 0;
238-
#endif
239-
}
240-
241-
#if NET
242200
private static readonly SearchValues<char> s_asciiLetters = SearchValues.Create("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
243-
#endif
201+
244202
internal static bool ContainsAnyAsciiLetters(ReadOnlySpan<char> s)
245203
{
246-
Debug.Assert(IsAllAscii(s));
204+
Debug.Assert(Ascii.IsValid(s));
247205

248-
#if NET
249206
return s.ContainsAny(s_asciiLetters);
250-
#else
251-
foreach (char c in s)
252-
{
253-
Debug.Assert(c <= 0x7f);
254-
if ((uint)((c | 0x20) - 'a') <= (uint)('z' - 'a'))
255-
{
256-
return true;
257-
}
258-
}
259-
return false;
260-
#endif
261207
}
262208

263209
internal static bool HasSufficientUniquenessFactor(HashSet<string> set, ReadOnlySpan<string> uniqueStrings, int acceptableNonUniqueCount)

src/libraries/System.Collections.Immutable/tests/Frozen/KeyAnalyzerTests.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,6 @@ public static void FullCaseInsensitiveAscii()
221221
Assert.True(r.AllAsciiIfIgnoreCase);
222222
}
223223

224-
[Fact]
225-
public static void IsAllAscii()
226-
{
227-
Assert.True(KeyAnalyzer.IsAllAscii("abc".AsSpan()));
228-
Assert.True(KeyAnalyzer.IsAllAscii("abcdefghij".AsSpan()));
229-
Assert.False(KeyAnalyzer.IsAllAscii("abcdéfghij".AsSpan()));
230-
}
231-
232224
[Fact]
233225
public static void ContainsAnyLetters()
234226
{

src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<!-- This test library intentionally references an inbox P2P as it needs the implementation, instead of the contract.
99
Suppress the NU1511 warning in the whole project as putting it on a P2P doesn't work: https://github.com/NuGet/Home/issues/14121 -->
1010
<NoWarn>$(NoWarn);NU1511</NoWarn>
11+
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
1112
</PropertyGroup>
1213

1314
<ItemGroup>

0 commit comments

Comments
 (0)