Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions src/libraries/Common/src/System/Buffers/SearchValuesPolyfills.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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.Text;

namespace System.Buffers
{
/// <summary>Provides downlevel polyfills for SearchValues APIs.</summary>
internal abstract class SearchValues<T>
{
public abstract bool Contains(T value);

public abstract int IndexOfAny(ReadOnlySpan<char> span);

public abstract int IndexOfAnyExcept(ReadOnlySpan<char> span);
}

internal static class SearchValues
{
public static SearchValues<char> Create(string values) =>
Create(values.AsSpan());

public static SearchValues<char> Create(ReadOnlySpan<char> values) =>
new CharSearchValuesPolyfill(values);

public static int IndexOfAny(this ReadOnlySpan<char> span, SearchValues<char> values) =>
values.IndexOfAny(span);

public static int IndexOfAnyExcept(this ReadOnlySpan<char> span, SearchValues<char> values) =>
values.IndexOfAnyExcept(span);

public static bool ContainsAny(this ReadOnlySpan<char> span, SearchValues<char> values) =>
values.IndexOfAny(span) >= 0;

public static bool ContainsAnyExcept(this ReadOnlySpan<char> span, SearchValues<char> values) =>
values.IndexOfAnyExcept(span) >= 0;

internal sealed class CharSearchValuesPolyfill : SearchValues<char>
{
private readonly uint[] _ascii = new uint[8];
private readonly string _nonAscii;

public CharSearchValuesPolyfill(ReadOnlySpan<char> values)
{
StringBuilder? nonAscii = null;

foreach (char c in values)
{
if (c < 128)
{
uint offset = (uint)(c >> 5);
uint significantBit = 1u << c;
_ascii[offset] |= significantBit;
}
else
{
nonAscii ??= new();
nonAscii.Append(c);
}
}

_nonAscii = nonAscii?.ToString() ?? string.Empty;
Debug.Assert(_nonAscii.Length < 10, "Expected few non-ASCII characters at most.");
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Contains(char value)
{
uint offset = (uint)(value >> 5);
uint significantBit = 1u << value;
uint[] lookup = _ascii;

if (offset < (uint)lookup.Length)
{
return (lookup[offset] & significantBit) != 0;
}
else
{
return NonAsciiContains(value);
}
}

private bool NonAsciiContains(char value)
{
foreach (char c in _nonAscii)
{
if (c == value)
{
return true;
}
}

return false;
}

public override int IndexOfAny(ReadOnlySpan<char> span)
{
for (int i = 0; i < span.Length; i++)
{
if (Contains(span[i]))
{
return i;
}
}

return -1;
}

public override int IndexOfAnyExcept(ReadOnlySpan<char> span)
{
for (int i = 0; i < span.Length; i++)
{
if (!Contains(span[i]))
{
return i;
}
}

return -1;
}
}
}
}
22 changes: 22 additions & 0 deletions src/libraries/Common/src/System/MemoryExtensionsPolyfills.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System
{
/// <summary>Provides downlevel polyfills for span extension methods.</summary>
internal static class MemoryExtensionsPolyfills
{
public static bool ContainsAnyExcept(this ReadOnlySpan<char> span, char value)
{
for (int i = 0; i < span.Length; i++)
{
if (span[i] != value)
{
return true;
}
}

return false;
}
}
}
51 changes: 51 additions & 0 deletions src/libraries/Common/src/System/Text/AsciiPolyfills.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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;

namespace System.Text
{
/// <summary>Provides downlevel polyfills for Ascii helper APIs.</summary>
internal static class Ascii
{
public static bool IsValid(string value)
{
return IsValid(value.AsSpan());
}

public static unsafe bool IsValid(ReadOnlySpan<char> value)
{
fixed (char* src = value)
{
uint* ptrUInt32 = (uint*)src;
int length = value.Length;

while (length >= 4)
{
if (!AllCharsInUInt32AreAscii(ptrUInt32[0] | ptrUInt32[1]))
{
return false;
}

ptrUInt32 += 2;
length -= 4;
}

char* ptrChar = (char*)ptrUInt32;
while (length-- > 0)
{
char ch = *ptrChar++;
if (ch >= 0x80)
{
return false;
}
}
}

return true;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool AllCharsInUInt32AreAscii(uint value) => (value & ~0x007F_007Fu) == 0;
}
}
}
8 changes: 8 additions & 0 deletions src/libraries/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\ExceptionPolyfills.cs" Link="System\ExceptionPolyfills.cs" />
</ItemGroup>

<!-- Adds polyfills for Span-related functionality on non-.NETCoreApp builds -->
<ItemGroup Condition="'$(IncludeSpanPolyfills)' == 'true' and
'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\MemoryExtensionsPolyfills.cs" Link="System\MemoryExtensionsPolyfills.cs" />
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Buffers\SearchValuesPolyfills.cs" Link="System\Buffers\SearchValuesPolyfills.cs" />
<Compile Include="$(LibrariesProjectRoot)\Common\src\System\Text\AsciiPolyfills.cs" Link="System\Text\AsciiPolyfills.cs" />
</ItemGroup>

<!-- If a tfm doesn't target .NETCoreApp but uses the platform support attributes, then we include the
System.Runtime.Versioning*Platform* annotation attribute classes in the project as internal.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ private static char[] GetInvalidFileNameChars() => Path.GetInvalidFileNameChars(
private static char[] GetInvalidFilterChars() => GetInvalidFileNameChars()
.Where(c => c != '*' && c != '|' && c != '?').ToArray();

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

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

internal static bool HasInvalidFilterChars(string path) =>
path.AsSpan().ContainsAny(_invalidFilterChars);
#else
private static readonly char[] _invalidFileNameChars = GetInvalidFileNameChars();
private static readonly char[] _invalidFilterChars = GetInvalidFilterChars();

internal static bool HasInvalidPathChars(string path) =>
path.IndexOfAny(_invalidFileNameChars) >= 0;

internal static bool HasInvalidFilterChars(string path) =>
path.IndexOfAny(_invalidFilterChars) >= 0;
#endif

private static readonly char[] _pathSeparators = new[]
{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<EnableDefaultItems>true</EnableDefaultItems>
<IsPackable>true</IsPackable>
<PackageDescription>File provider for physical files for Microsoft.Extensions.FileProviders.</PackageDescription>
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
</PropertyGroup>

<ItemGroup>
Expand Down
7 changes: 6 additions & 1 deletion src/libraries/System.CodeDom/src/System.CodeDom.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Expand All @@ -10,13 +10,18 @@
<!-- TODO https://github.com/dotnet/runtime/issues/90400: Annotate for nullable reference types -->
<Nullable>disable</Nullable>
<NoWarn>$(NoWarn);nullable</NoWarn>
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
</PropertyGroup>

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

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
<PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
</ItemGroup>

<ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
<Compile Include="Microsoft\CSharp\CSharpCodeGenerator.cs" />
<Compile Include="Microsoft\CSharp\CSharpCodeGenerator.PlatformNotSupported.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.IO;

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

private CodeTypeDeclaration _currentClass;

internal void ValidateIdentifiers(CodeObject e)
Expand Down Expand Up @@ -941,13 +944,13 @@ private static void ValidateCodeDirective(CodeDirective e)

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

private static void ValidateRegionDirective(CodeRegionDirective e)
{
if (e.RegionText.IndexOfAny(s_newLineChars) != -1)
if (e.RegionText.AsSpan().ContainsAny(s_newLineChars))
throw new ArgumentException(SR.Format(SR.InvalidRegion, e.RegionText), nameof(e));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<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.

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>
<IncludeSpanPolyfills>true</IncludeSpanPolyfills>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;

namespace System.Collections.Frozen
{
Expand Down Expand Up @@ -77,7 +78,7 @@ public static unsafe int GetHashCodeOrdinal(ReadOnlySpan<char> s)
// useful if the string only contains ASCII characters
public static unsafe int GetHashCodeOrdinalIgnoreCaseAscii(ReadOnlySpan<char> s)
{
Debug.Assert(KeyAnalyzer.IsAllAscii(s));
Debug.Assert(Ascii.IsValid(s));

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