Skip to content

Commit a09317d

Browse files
authored
Polyfill SearchValues and optimize Activity, W3CPropagator (#119673)
1 parent 3720b8f commit a09317d

File tree

34 files changed

+323
-342
lines changed

34 files changed

+323
-342
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
using System.Text;
7+
8+
namespace System.Buffers
9+
{
10+
/// <summary>Provides downlevel polyfills for SearchValues APIs.</summary>
11+
internal abstract class SearchValues<T>
12+
{
13+
public abstract bool Contains(T value);
14+
15+
public abstract int IndexOfAny(ReadOnlySpan<char> span);
16+
17+
public abstract int IndexOfAnyExcept(ReadOnlySpan<char> span);
18+
}
19+
20+
internal static class SearchValues
21+
{
22+
public static SearchValues<char> Create(string values) =>
23+
Create(values.AsSpan());
24+
25+
public static SearchValues<char> Create(ReadOnlySpan<char> values) =>
26+
new CharSearchValuesPolyfill(values);
27+
28+
public static int IndexOfAny(this ReadOnlySpan<char> span, SearchValues<char> values) =>
29+
values.IndexOfAny(span);
30+
31+
public static int IndexOfAnyExcept(this ReadOnlySpan<char> span, SearchValues<char> values) =>
32+
values.IndexOfAnyExcept(span);
33+
34+
public static bool ContainsAny(this ReadOnlySpan<char> span, SearchValues<char> values) =>
35+
values.IndexOfAny(span) >= 0;
36+
37+
public static bool ContainsAnyExcept(this ReadOnlySpan<char> span, SearchValues<char> values) =>
38+
values.IndexOfAnyExcept(span) >= 0;
39+
40+
internal sealed class CharSearchValuesPolyfill : SearchValues<char>
41+
{
42+
private readonly uint[] _ascii = new uint[8];
43+
private readonly string _nonAscii;
44+
45+
public CharSearchValuesPolyfill(ReadOnlySpan<char> values)
46+
{
47+
StringBuilder? nonAscii = null;
48+
49+
foreach (char c in values)
50+
{
51+
if (c < 128)
52+
{
53+
uint offset = (uint)(c >> 5);
54+
uint significantBit = 1u << c;
55+
_ascii[offset] |= significantBit;
56+
}
57+
else
58+
{
59+
nonAscii ??= new();
60+
nonAscii.Append(c);
61+
}
62+
}
63+
64+
_nonAscii = nonAscii?.ToString() ?? string.Empty;
65+
Debug.Assert(_nonAscii.Length < 10, "Expected few non-ASCII characters at most.");
66+
}
67+
68+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
69+
public override bool Contains(char value)
70+
{
71+
uint offset = (uint)(value >> 5);
72+
uint significantBit = 1u << value;
73+
uint[] lookup = _ascii;
74+
75+
if (offset < (uint)lookup.Length)
76+
{
77+
return (lookup[offset] & significantBit) != 0;
78+
}
79+
else
80+
{
81+
return NonAsciiContains(value);
82+
}
83+
}
84+
85+
private bool NonAsciiContains(char value)
86+
{
87+
foreach (char c in _nonAscii)
88+
{
89+
if (c == value)
90+
{
91+
return true;
92+
}
93+
}
94+
95+
return false;
96+
}
97+
98+
public override int IndexOfAny(ReadOnlySpan<char> span)
99+
{
100+
for (int i = 0; i < span.Length; i++)
101+
{
102+
if (Contains(span[i]))
103+
{
104+
return i;
105+
}
106+
}
107+
108+
return -1;
109+
}
110+
111+
public override int IndexOfAnyExcept(ReadOnlySpan<char> span)
112+
{
113+
for (int i = 0; i < span.Length; i++)
114+
{
115+
if (!Contains(span[i]))
116+
{
117+
return i;
118+
}
119+
}
120+
121+
return -1;
122+
}
123+
}
124+
}
125+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System
5+
{
6+
/// <summary>Provides downlevel polyfills for span extension methods.</summary>
7+
internal static class MemoryExtensionsPolyfills
8+
{
9+
public static bool ContainsAnyExcept(this ReadOnlySpan<char> span, char value)
10+
{
11+
for (int i = 0; i < span.Length; i++)
12+
{
13+
if (span[i] != value)
14+
{
15+
return true;
16+
}
17+
}
18+
19+
return false;
20+
}
21+
}
22+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
namespace System.Text
7+
{
8+
/// <summary>Provides downlevel polyfills for Ascii helper APIs.</summary>
9+
internal static class Ascii
10+
{
11+
public static bool IsValid(string value)
12+
{
13+
return IsValid(value.AsSpan());
14+
}
15+
16+
public static unsafe bool IsValid(ReadOnlySpan<char> value)
17+
{
18+
fixed (char* src = value)
19+
{
20+
uint* ptrUInt32 = (uint*)src;
21+
int length = value.Length;
22+
23+
while (length >= 4)
24+
{
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+
}
42+
}
43+
}
44+
45+
return true;
46+
47+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48+
static bool AllCharsInUInt32AreAscii(uint value) => (value & ~0x007F_007Fu) == 0;
49+
}
50+
}
51+
}

src/libraries/Directory.Build.targets

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@
188188
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\ExceptionPolyfills.cs" Link="System\ExceptionPolyfills.cs" />
189189
</ItemGroup>
190190

191+
<!-- Adds polyfills for Span-related functionality on non-.NETCoreApp builds -->
192+
<ItemGroup Condition="'$(IncludeSpanPolyfills)' == 'true' and
193+
'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
194+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\MemoryExtensionsPolyfills.cs" Link="System\MemoryExtensionsPolyfills.cs" />
195+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Buffers\SearchValuesPolyfills.cs" Link="System\Buffers\SearchValuesPolyfills.cs" />
196+
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Text\AsciiPolyfills.cs" Link="System\Text\AsciiPolyfills.cs" />
197+
</ItemGroup>
198+
191199
<!-- If a tfm doesn't target .NETCoreApp but uses the platform support attributes, then we include the
192200
System.Runtime.Versioning*Platform* annotation attribute classes in the project as internal.
193201

src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Internal/PathUtils.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ private static char[] GetInvalidFileNameChars() => Path.GetInvalidFileNameChars(
1717
private static char[] GetInvalidFilterChars() => GetInvalidFileNameChars()
1818
.Where(c => c != '*' && c != '|' && c != '?').ToArray();
1919

20-
#if NET
2120
private static readonly SearchValues<char> _invalidFileNameChars = SearchValues.Create(GetInvalidFileNameChars());
2221
private static readonly SearchValues<char> _invalidFilterChars = SearchValues.Create(GetInvalidFilterChars());
2322

@@ -26,16 +25,6 @@ internal static bool HasInvalidPathChars(string path) =>
2625

2726
internal static bool HasInvalidFilterChars(string path) =>
2827
path.AsSpan().ContainsAny(_invalidFilterChars);
29-
#else
30-
private static readonly char[] _invalidFileNameChars = GetInvalidFileNameChars();
31-
private static readonly char[] _invalidFilterChars = GetInvalidFilterChars();
32-
33-
internal static bool HasInvalidPathChars(string path) =>
34-
path.IndexOfAny(_invalidFileNameChars) >= 0;
35-
36-
internal static bool HasInvalidFilterChars(string path) =>
37-
path.IndexOfAny(_invalidFilterChars) >= 0;
38-
#endif
3928

4029
private static readonly char[] _pathSeparators = new[]
4130
{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};

src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<EnableDefaultItems>true</EnableDefaultItems>
99
<IsPackable>true</IsPackable>
1010
<PackageDescription>File provider for physical files for Microsoft.Extensions.FileProviders.</PackageDescription>
11+
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
1112
</PropertyGroup>
1213

1314
<ItemGroup>

src/libraries/System.CodeDom/src/System.CodeDom.csproj

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
44
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -10,13 +10,18 @@
1010
<!-- TODO https://github.com/dotnet/runtime/issues/90400: Annotate for nullable reference types -->
1111
<Nullable>disable</Nullable>
1212
<NoWarn>$(NoWarn);nullable</NoWarn>
13+
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
1314
</PropertyGroup>
1415

1516
<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
1617
<PropertyGroup>
1718
<IsPartialFacadeAssembly Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETFramework'">true</IsPartialFacadeAssembly>
1819
</PropertyGroup>
1920

21+
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
22+
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
23+
</ItemGroup>
24+
2025
<ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
2126
<Compile Include="Microsoft\CSharp\CSharpCodeGenerator.cs" />
2227
<Compile Include="Microsoft\CSharp\CSharpCodeGenerator.PlatformNotSupported.cs" />

src/libraries/System.CodeDom/src/System/CodeDom/Compiler/CodeValidator.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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.Buffers;
45
using System.IO;
56

67
namespace System.CodeDom.Compiler
@@ -12,7 +13,9 @@ namespace System.CodeDom.Compiler
1213
// You can pass in any node in the tree that is a subclass of CodeObject.
1314
internal sealed class CodeValidator
1415
{
15-
private static readonly char[] s_newLineChars = new char[] { '\r', '\n', '\u2028', '\u2029', '\u0085' };
16+
private static readonly SearchValues<char> s_newLineChars = SearchValues.Create("\r\n\u2028\u2029\u0085");
17+
private static readonly SearchValues<char> s_invalidPathChars = SearchValues.Create(Path.GetInvalidPathChars());
18+
1619
private CodeTypeDeclaration _currentClass;
1720

1821
internal void ValidateIdentifiers(CodeObject e)
@@ -941,13 +944,13 @@ private static void ValidateCodeDirective(CodeDirective e)
941944

942945
private static void ValidateChecksumPragma(CodeChecksumPragma e)
943946
{
944-
if (e.FileName.IndexOfAny(Path.GetInvalidPathChars()) != -1)
947+
if (e.FileName.AsSpan().ContainsAny(s_invalidPathChars))
945948
throw new ArgumentException(SR.Format(SR.InvalidPathCharsInChecksum, e.FileName), nameof(e));
946949
}
947950

948951
private static void ValidateRegionDirective(CodeRegionDirective e)
949952
{
950-
if (e.RegionText.IndexOfAny(s_newLineChars) != -1)
953+
if (e.RegionText.AsSpan().ContainsAny(s_newLineChars))
951954
throw new ArgumentException(SR.Format(SR.InvalidRegion, e.RegionText), nameof(e));
952955
}
953956

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

0 commit comments

Comments
 (0)