[master] Update dependencies from dotnet/coreclr (dotnet/corefx#41280)
authordotnet-maestro[bot] <42748379+dotnet-maestro[bot]@users.noreply.github.com>
Thu, 3 Oct 2019 00:36:36 +0000 (00:36 +0000)
committerGitHub <noreply@github.com>
Thu, 3 Oct 2019 00:36:36 +0000 (00:36 +0000)
* Add unit tests for Utf8Span, react to Utf8String changes

* Regenerating the reference sources for System.Runtime.Intrinsics.Experimental

* Update dependencies from https://github.com/dotnet/coreclr build 20191001.3

- Microsoft.NET.Sdk.IL - 5.0.0-alpha1.19501.3
- Microsoft.NETCore.ILAsm - 5.0.0-alpha1.19501.3
- Microsoft.NETCore.Runtime.CoreCLR - 5.0.0-alpha1.19501.3

* Move corefx alpine build containers to 3.9

Commit migrated from https://github.com/dotnet/corefx/commit/c8c2917540a568869ba761afa6e4dba6edeb433b

24 files changed:
eng/pipelines/libraries/.azure-ci.yml
eng/pipelines/libraries/outerloop.yml
src/libraries/System.Runtime.Intrinsics.Experimental/ref/System.Runtime.Intrinsics.Experimental.cs
src/libraries/System.Runtime.Intrinsics.Experimental/src/ApiCompatBaseline.txt [new file with mode: 0644]
src/libraries/System.Runtime.Intrinsics.Experimental/src/MatchingRefApiCompatBaseline.txt [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.cs
src/libraries/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj
src/libraries/System.Utf8String.Experimental/src/System/IO/Utf8StringStream.cs
src/libraries/System.Utf8String.Experimental/src/System/Net/Http/Utf8StringContent.cs
src/libraries/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj
src/libraries/System.Utf8String.Experimental/tests/System/BoundedUtf8Span.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/RangeEqualityComparer.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8ExtensionsTests.cs
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Comparison.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Conversion.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Ctor.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Enumeration.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.TestData.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.TestData.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.TestData.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.cs [new file with mode: 0644]
src/libraries/System.Utf8String.Experimental/tests/System/Utf8TestUtilities.cs

index e8cbd85..bf34c13 100644 (file)
@@ -32,7 +32,7 @@ resources:
     image: microsoft/dotnet-buildtools-prereqs:centos-6-376e1a3-20174311014331
 
   - container: alpine_36_container
-    image: microsoft/dotnet-buildtools-prereqs:alpine-3.6-WithNode-f4d3fe3-20181213005010
+    image: mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.9-WithNode-0fc54a3-20190918214015
 
   - container: alpine_37_arm64_container
     image: microsoft/dotnet-buildtools-prereqs:ubuntu-16.04-cross-arm64-alpine10fcdcf-20190208200917
index 2d5ffd6..00c22ed 100644 (file)
@@ -17,7 +17,7 @@ resources:
     image: microsoft/dotnet-buildtools-prereqs:centos-6-376e1a3-20174311014331
 
   - container: alpine_36_container
-    image: microsoft/dotnet-buildtools-prereqs:alpine-3.6-WithNode-f4d3fe3-20181213005010
+    image: mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.9-WithNode-0fc54a3-20190918214015
 
   - container: alpine_37_arm64_container
     image: microsoft/dotnet-buildtools-prereqs:ubuntu-16.04-cross-arm64-alpine10fcdcf-20190208200917
index 4210250..4a2ed82 100644 (file)
 // Changes to this file must follow the http://aka.ms/api-review process.
 // ------------------------------------------------------------------------------
 
-namespace System.Runtime.Intrinsics.Arm.Arm64
+namespace System.Runtime.Intrinsics.Arm
 {
     [System.CLSCompliantAttribute(false)]
-    public static partial class Aes
+    public abstract partial class AdvSimd : System.Runtime.Intrinsics.Arm.ArmBase
     {
-        public static bool IsSupported { get { throw null; } }
+        internal AdvSimd() { }
+        public static new bool IsSupported { get { throw null; } }
+        public static System.Runtime.Intrinsics.Vector128<ushort> Abs(System.Runtime.Intrinsics.Vector128<short> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> Abs(System.Runtime.Intrinsics.Vector128<int> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<byte> Abs(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<float> Abs(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<ushort> Abs(System.Runtime.Intrinsics.Vector64<short> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<uint> Abs(System.Runtime.Intrinsics.Vector64<int> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<byte> Abs(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<float> Abs(System.Runtime.Intrinsics.Vector64<float> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<float> AbsScalar(System.Runtime.Intrinsics.Vector64<float> value) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<byte> Add(System.Runtime.Intrinsics.Vector128<byte> left, System.Runtime.Intrinsics.Vector128<byte> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<short> Add(System.Runtime.Intrinsics.Vector128<short> left, System.Runtime.Intrinsics.Vector128<short> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<int> Add(System.Runtime.Intrinsics.Vector128<int> left, System.Runtime.Intrinsics.Vector128<int> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<long> Add(System.Runtime.Intrinsics.Vector128<long> left, System.Runtime.Intrinsics.Vector128<long> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<sbyte> Add(System.Runtime.Intrinsics.Vector128<sbyte> left, System.Runtime.Intrinsics.Vector128<sbyte> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<float> Add(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<ushort> Add(System.Runtime.Intrinsics.Vector128<ushort> left, System.Runtime.Intrinsics.Vector128<ushort> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> Add(System.Runtime.Intrinsics.Vector128<uint> left, System.Runtime.Intrinsics.Vector128<uint> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<ulong> Add(System.Runtime.Intrinsics.Vector128<ulong> left, System.Runtime.Intrinsics.Vector128<ulong> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<byte> Add(System.Runtime.Intrinsics.Vector64<byte> left, System.Runtime.Intrinsics.Vector64<byte> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<short> Add(System.Runtime.Intrinsics.Vector64<short> left, System.Runtime.Intrinsics.Vector64<short> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<int> Add(System.Runtime.Intrinsics.Vector64<int> left, System.Runtime.Intrinsics.Vector64<int> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<sbyte> Add(System.Runtime.Intrinsics.Vector64<sbyte> left, System.Runtime.Intrinsics.Vector64<sbyte> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<float> Add(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<ushort> Add(System.Runtime.Intrinsics.Vector64<ushort> left, System.Runtime.Intrinsics.Vector64<ushort> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<uint> Add(System.Runtime.Intrinsics.Vector64<uint> left, System.Runtime.Intrinsics.Vector64<uint> right) { throw null; }
+        public static System.Runtime.Intrinsics.Vector64<float> AddScalar(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<byte> LoadVector128(byte* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<double> LoadVector128(double* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<short> LoadVector128(short* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<int> LoadVector128(int* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<long> LoadVector128(long* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<sbyte> LoadVector128(sbyte* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<float> LoadVector128(float* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<ushort> LoadVector128(ushort* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<uint> LoadVector128(uint* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector128<ulong> LoadVector128(ulong* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<byte> LoadVector64(byte* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<short> LoadVector64(short* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<int> LoadVector64(int* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<sbyte> LoadVector64(sbyte* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<float> LoadVector64(float* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<ushort> LoadVector64(ushort* address) { throw null; }
+        public unsafe static System.Runtime.Intrinsics.Vector64<uint> LoadVector64(uint* address) { throw null; }
+        public new abstract partial class Arm64 : System.Runtime.Intrinsics.Arm.ArmBase.Arm64
+        {
+            internal Arm64() { }
+            public static new bool IsSupported { get { throw null; } }
+            public static System.Runtime.Intrinsics.Vector128<double> Abs(System.Runtime.Intrinsics.Vector128<double> value) { throw null; }
+            public static System.Runtime.Intrinsics.Vector128<ulong> Abs(System.Runtime.Intrinsics.Vector128<long> value) { throw null; }
+            public static System.Runtime.Intrinsics.Vector128<double> Add(System.Runtime.Intrinsics.Vector128<double> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
+        }
+    }
+    [System.CLSCompliantAttribute(false)]
+    public abstract partial class Aes : System.Runtime.Intrinsics.Arm.ArmBase
+    {
+        internal Aes() { }
+        public static new bool IsSupported { get { throw null; } }
         public static System.Runtime.Intrinsics.Vector128<byte> Decrypt(System.Runtime.Intrinsics.Vector128<byte> value, System.Runtime.Intrinsics.Vector128<byte> roundKey) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<byte> Encrypt(System.Runtime.Intrinsics.Vector128<byte> value, System.Runtime.Intrinsics.Vector128<byte> roundKey) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<byte> InverseMixColumns(System.Runtime.Intrinsics.Vector128<byte> value) { throw null; }
         public static System.Runtime.Intrinsics.Vector128<byte> MixColumns(System.Runtime.Intrinsics.Vector128<byte> value) { throw null; }
     }
     [System.CLSCompliantAttribute(false)]
-    public static partial class Base
+    public abstract partial class ArmBase
     {
+        internal ArmBase() { }
         public static bool IsSupported { get { throw null; } }
-        public static int LeadingSignCount(int value) { throw null; }
-        public static int LeadingSignCount(long value) { throw null; }
         public static int LeadingZeroCount(int value) { throw null; }
-        public static int LeadingZeroCount(long value) { throw null; }
         public static int LeadingZeroCount(uint value) { throw null; }
-        public static int LeadingZeroCount(ulong value) { throw null; }
+        public abstract partial class Arm64
+        {
+            internal Arm64() { }
+            public static bool IsSupported { get { throw null; } }
+            public static int LeadingSignCount(int value) { throw null; }
+            public static int LeadingSignCount(long value) { throw null; }
+            public static int LeadingZeroCount(long value) { throw null; }
+            public static int LeadingZeroCount(ulong value) { throw null; }
+        }
     }
     [System.CLSCompliantAttribute(false)]
-    public static partial class Sha1
+    public abstract partial class Sha1 : System.Runtime.Intrinsics.Arm.ArmBase
     {
-        public static bool IsSupported { get { throw null; } }
+        internal Sha1() { }
+        public static new bool IsSupported { get { throw null; } }
         public static uint FixedRotate(uint hash_e) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> HashChoose(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> HashMajority(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> HashParity(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> SchedulePart1(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w4_7, System.Runtime.Intrinsics.Vector128<uint> w8_11) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> SchedulePart2(System.Runtime.Intrinsics.Vector128<uint> tw0_3, System.Runtime.Intrinsics.Vector128<uint> w12_15) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> HashUpdateChoose(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> HashUpdateMajority(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> HashUpdateParity(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, uint hash_e, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> ScheduleUpdate0(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w4_7, System.Runtime.Intrinsics.Vector128<uint> w8_11) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> ScheduleUpdate1(System.Runtime.Intrinsics.Vector128<uint> tw0_3, System.Runtime.Intrinsics.Vector128<uint> w12_15) { throw null; }
     }
     [System.CLSCompliantAttribute(false)]
-    public static partial class Sha256
+    public abstract partial class Sha256 : System.Runtime.Intrinsics.Arm.ArmBase
     {
-        public static bool IsSupported { get { throw null; } }
-        public static System.Runtime.Intrinsics.Vector128<uint> HashLower(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, System.Runtime.Intrinsics.Vector128<uint> hash_efgh, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> HashUpper(System.Runtime.Intrinsics.Vector128<uint> hash_efgh, System.Runtime.Intrinsics.Vector128<uint> hash_abcd, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> SchedulePart1(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w4_7) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> SchedulePart2(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w8_11, System.Runtime.Intrinsics.Vector128<uint> w12_15) { throw null; }
-    }
-    [System.CLSCompliantAttribute(false)]
-    public static partial class Simd
-    {
-        public static bool IsSupported { get { throw null; } }
-        public static System.Runtime.Intrinsics.Vector128<double> Abs(System.Runtime.Intrinsics.Vector128<double> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ushort> Abs(System.Runtime.Intrinsics.Vector128<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> Abs(System.Runtime.Intrinsics.Vector128<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ulong> Abs(System.Runtime.Intrinsics.Vector128<long> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> Abs(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Abs(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<ushort> Abs(System.Runtime.Intrinsics.Vector64<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<uint> Abs(System.Runtime.Intrinsics.Vector64<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> Abs(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Abs(System.Runtime.Intrinsics.Vector64<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Add<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Add<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> AndNot<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> AndNot<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> And<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> And<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> BitwiseSelect<T>(System.Runtime.Intrinsics.Vector128<T> sel, System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> BitwiseSelect<T>(System.Runtime.Intrinsics.Vector64<T> sel, System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareEqualZero<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareEqualZero<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareEqual<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareEqual<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareGreaterThanOrEqualZero<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareGreaterThanOrEqualZero<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareGreaterThanOrEqual<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareGreaterThanOrEqual<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareGreaterThanZero<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareGreaterThanZero<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareGreaterThan<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareGreaterThan<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareLessThanOrEqualZero<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareLessThanOrEqualZero<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareLessThanZero<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareLessThanZero<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> CompareTest<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> CompareTest<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Divide(System.Runtime.Intrinsics.Vector128<double> left, System.Runtime.Intrinsics.Vector128<double> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Divide(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Divide(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
-        public static T Extract<T>(System.Runtime.Intrinsics.Vector128<T> vector, byte index) where T : struct { throw null; }
-        public static T Extract<T>(System.Runtime.Intrinsics.Vector64<T> vector, byte index) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Insert<T>(System.Runtime.Intrinsics.Vector128<T> vector, byte index, T data) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Insert<T>(System.Runtime.Intrinsics.Vector64<T> vector, byte index, T data) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> LeadingSignCount(System.Runtime.Intrinsics.Vector128<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> LeadingSignCount(System.Runtime.Intrinsics.Vector128<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> LeadingSignCount(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> LeadingSignCount(System.Runtime.Intrinsics.Vector64<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> LeadingSignCount(System.Runtime.Intrinsics.Vector64<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> LeadingSignCount(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<byte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ushort> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<ushort> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> LeadingZeroCount(System.Runtime.Intrinsics.Vector128<uint> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<byte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<ushort> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<ushort> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<uint> LeadingZeroCount(System.Runtime.Intrinsics.Vector64<uint> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> Max(System.Runtime.Intrinsics.Vector128<byte> left, System.Runtime.Intrinsics.Vector128<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Max(System.Runtime.Intrinsics.Vector128<double> left, System.Runtime.Intrinsics.Vector128<double> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> Max(System.Runtime.Intrinsics.Vector128<short> left, System.Runtime.Intrinsics.Vector128<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> Max(System.Runtime.Intrinsics.Vector128<int> left, System.Runtime.Intrinsics.Vector128<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> Max(System.Runtime.Intrinsics.Vector128<sbyte> left, System.Runtime.Intrinsics.Vector128<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Max(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ushort> Max(System.Runtime.Intrinsics.Vector128<ushort> left, System.Runtime.Intrinsics.Vector128<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> Max(System.Runtime.Intrinsics.Vector128<uint> left, System.Runtime.Intrinsics.Vector128<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> Max(System.Runtime.Intrinsics.Vector64<byte> left, System.Runtime.Intrinsics.Vector64<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> Max(System.Runtime.Intrinsics.Vector64<short> left, System.Runtime.Intrinsics.Vector64<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> Max(System.Runtime.Intrinsics.Vector64<int> left, System.Runtime.Intrinsics.Vector64<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> Max(System.Runtime.Intrinsics.Vector64<sbyte> left, System.Runtime.Intrinsics.Vector64<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Max(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<ushort> Max(System.Runtime.Intrinsics.Vector64<ushort> left, System.Runtime.Intrinsics.Vector64<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<uint> Max(System.Runtime.Intrinsics.Vector64<uint> left, System.Runtime.Intrinsics.Vector64<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> Min(System.Runtime.Intrinsics.Vector128<byte> left, System.Runtime.Intrinsics.Vector128<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Min(System.Runtime.Intrinsics.Vector128<double> left, System.Runtime.Intrinsics.Vector128<double> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> Min(System.Runtime.Intrinsics.Vector128<short> left, System.Runtime.Intrinsics.Vector128<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> Min(System.Runtime.Intrinsics.Vector128<int> left, System.Runtime.Intrinsics.Vector128<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> Min(System.Runtime.Intrinsics.Vector128<sbyte> left, System.Runtime.Intrinsics.Vector128<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Min(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ushort> Min(System.Runtime.Intrinsics.Vector128<ushort> left, System.Runtime.Intrinsics.Vector128<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> Min(System.Runtime.Intrinsics.Vector128<uint> left, System.Runtime.Intrinsics.Vector128<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> Min(System.Runtime.Intrinsics.Vector64<byte> left, System.Runtime.Intrinsics.Vector64<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> Min(System.Runtime.Intrinsics.Vector64<short> left, System.Runtime.Intrinsics.Vector64<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> Min(System.Runtime.Intrinsics.Vector64<int> left, System.Runtime.Intrinsics.Vector64<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> Min(System.Runtime.Intrinsics.Vector64<sbyte> left, System.Runtime.Intrinsics.Vector64<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Min(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<ushort> Min(System.Runtime.Intrinsics.Vector64<ushort> left, System.Runtime.Intrinsics.Vector64<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<uint> Min(System.Runtime.Intrinsics.Vector64<uint> left, System.Runtime.Intrinsics.Vector64<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> Multiply(System.Runtime.Intrinsics.Vector128<byte> left, System.Runtime.Intrinsics.Vector128<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Multiply(System.Runtime.Intrinsics.Vector128<double> left, System.Runtime.Intrinsics.Vector128<double> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> Multiply(System.Runtime.Intrinsics.Vector128<short> left, System.Runtime.Intrinsics.Vector128<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> Multiply(System.Runtime.Intrinsics.Vector128<int> left, System.Runtime.Intrinsics.Vector128<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> Multiply(System.Runtime.Intrinsics.Vector128<sbyte> left, System.Runtime.Intrinsics.Vector128<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Multiply(System.Runtime.Intrinsics.Vector128<float> left, System.Runtime.Intrinsics.Vector128<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<ushort> Multiply(System.Runtime.Intrinsics.Vector128<ushort> left, System.Runtime.Intrinsics.Vector128<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<uint> Multiply(System.Runtime.Intrinsics.Vector128<uint> left, System.Runtime.Intrinsics.Vector128<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> Multiply(System.Runtime.Intrinsics.Vector64<byte> left, System.Runtime.Intrinsics.Vector64<byte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> Multiply(System.Runtime.Intrinsics.Vector64<short> left, System.Runtime.Intrinsics.Vector64<short> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> Multiply(System.Runtime.Intrinsics.Vector64<int> left, System.Runtime.Intrinsics.Vector64<int> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> Multiply(System.Runtime.Intrinsics.Vector64<sbyte> left, System.Runtime.Intrinsics.Vector64<sbyte> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Multiply(System.Runtime.Intrinsics.Vector64<float> left, System.Runtime.Intrinsics.Vector64<float> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<ushort> Multiply(System.Runtime.Intrinsics.Vector64<ushort> left, System.Runtime.Intrinsics.Vector64<ushort> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<uint> Multiply(System.Runtime.Intrinsics.Vector64<uint> left, System.Runtime.Intrinsics.Vector64<uint> right) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Negate(System.Runtime.Intrinsics.Vector128<double> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<short> Negate(System.Runtime.Intrinsics.Vector128<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<int> Negate(System.Runtime.Intrinsics.Vector128<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<long> Negate(System.Runtime.Intrinsics.Vector128<long> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> Negate(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Negate(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<short> Negate(System.Runtime.Intrinsics.Vector64<short> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<int> Negate(System.Runtime.Intrinsics.Vector64<int> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> Negate(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Negate(System.Runtime.Intrinsics.Vector64<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Not<T>(System.Runtime.Intrinsics.Vector128<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Not<T>(System.Runtime.Intrinsics.Vector64<T> value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> OrNot<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> OrNot<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Or<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Or<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<byte> PopCount(System.Runtime.Intrinsics.Vector128<byte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<sbyte> PopCount(System.Runtime.Intrinsics.Vector128<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<byte> PopCount(System.Runtime.Intrinsics.Vector64<byte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<sbyte> PopCount(System.Runtime.Intrinsics.Vector64<sbyte> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> SetAllVector128<T>(T value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> SetAllVector64<T>(T value) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<double> Sqrt(System.Runtime.Intrinsics.Vector128<double> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<float> Sqrt(System.Runtime.Intrinsics.Vector128<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<float> Sqrt(System.Runtime.Intrinsics.Vector64<float> value) { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Subtract<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Subtract<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector128<T> Xor<T>(System.Runtime.Intrinsics.Vector128<T> left, System.Runtime.Intrinsics.Vector128<T> right) where T : struct { throw null; }
-        public static System.Runtime.Intrinsics.Vector64<T> Xor<T>(System.Runtime.Intrinsics.Vector64<T> left, System.Runtime.Intrinsics.Vector64<T> right) where T : struct { throw null; }
+        internal Sha256() { }
+        public static new bool IsSupported { get { throw null; } }
+        public static System.Runtime.Intrinsics.Vector128<uint> HashUpdate1(System.Runtime.Intrinsics.Vector128<uint> hash_abcd, System.Runtime.Intrinsics.Vector128<uint> hash_efgh, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> HashUpdate2(System.Runtime.Intrinsics.Vector128<uint> hash_efgh, System.Runtime.Intrinsics.Vector128<uint> hash_abcd, System.Runtime.Intrinsics.Vector128<uint> wk) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> ScheduleUpdate0(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w4_7) { throw null; }
+        public static System.Runtime.Intrinsics.Vector128<uint> ScheduleUpdate1(System.Runtime.Intrinsics.Vector128<uint> w0_3, System.Runtime.Intrinsics.Vector128<uint> w8_11, System.Runtime.Intrinsics.Vector128<uint> w12_15) { throw null; }
     }
 }
diff --git a/src/libraries/System.Runtime.Intrinsics.Experimental/src/ApiCompatBaseline.txt b/src/libraries/System.Runtime.Intrinsics.Experimental/src/ApiCompatBaseline.txt
new file mode 100644 (file)
index 0000000..55dbd30
--- /dev/null
@@ -0,0 +1,3 @@
+Compat issues with assembly System.Runtime.Intrinsics.Experimental:
+CannotSealType : Type 'System.Runtime.Intrinsics.Arm.AdvSimd.Arm64' is effectively (has a private constructor) sealed in the reference but not sealed in the implementation.
+Total Issues: 1
diff --git a/src/libraries/System.Runtime.Intrinsics.Experimental/src/MatchingRefApiCompatBaseline.txt b/src/libraries/System.Runtime.Intrinsics.Experimental/src/MatchingRefApiCompatBaseline.txt
new file mode 100644 (file)
index 0000000..16c4416
--- /dev/null
@@ -0,0 +1,4 @@
+Compat issues with assembly System.Runtime.Intrinsics.Experimental:
+CannotSealType : Type 'System.Runtime.Intrinsics.Arm.AdvSimd.Arm64' is effectively (has a private constructor) sealed in the reference but not sealed in the implementation.
+MembersMustExist : Member 'System.Runtime.Intrinsics.Arm.AdvSimd.Arm64..ctor()' does not exist in the reference but it does exist in the implementation.
+Total Issues: 2
index c48f100..75f2850 100644 (file)
@@ -12,37 +12,37 @@ namespace System
         private readonly int _dummyPrimitive;
         public int CompareTo(System.Char8 other) { throw null; }
         public bool Equals(System.Char8 other) { throw null; }
-        public override bool Equals(object obj) { throw null; }
+        public override bool Equals(object? obj) { throw null; }
         public override int GetHashCode() { throw null; }
         public static bool operator ==(System.Char8 left, System.Char8 right) { throw null; }
-        public static explicit operator System.Char8 (char value) { throw null; }
-        public static explicit operator char (System.Char8 value) { throw null; }
+        public static explicit operator System.Char8(char value) { throw null; }
+        public static explicit operator char(System.Char8 value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static explicit operator sbyte (System.Char8 value) { throw null; }
-        public static explicit operator System.Char8 (short value) { throw null; }
-        public static explicit operator System.Char8 (int value) { throw null; }
-        public static explicit operator System.Char8 (long value) { throw null; }
+        public static explicit operator sbyte(System.Char8 value) { throw null; }
+        public static explicit operator System.Char8(short value) { throw null; }
+        public static explicit operator System.Char8(int value) { throw null; }
+        public static explicit operator System.Char8(long value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static explicit operator System.Char8 (sbyte value) { throw null; }
+        public static explicit operator System.Char8(sbyte value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static explicit operator System.Char8 (ushort value) { throw null; }
+        public static explicit operator System.Char8(ushort value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static explicit operator System.Char8 (uint value) { throw null; }
+        public static explicit operator System.Char8(uint value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static explicit operator System.Char8 (ulong value) { throw null; }
+        public static explicit operator System.Char8(ulong value) { throw null; }
         public static bool operator >(System.Char8 left, System.Char8 right) { throw null; }
         public static bool operator >=(System.Char8 left, System.Char8 right) { throw null; }
-        public static implicit operator System.Char8 (byte value) { throw null; }
-        public static implicit operator byte (System.Char8 value) { throw null; }
-        public static implicit operator short (System.Char8 value) { throw null; }
-        public static implicit operator int (System.Char8 value) { throw null; }
-        public static implicit operator long (System.Char8 value) { throw null; }
+        public static implicit operator System.Char8(byte value) { throw null; }
+        public static implicit operator byte(System.Char8 value) { throw null; }
+        public static implicit operator short(System.Char8 value) { throw null; }
+        public static implicit operator int(System.Char8 value) { throw null; }
+        public static implicit operator long(System.Char8 value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static implicit operator ushort (System.Char8 value) { throw null; }
+        public static implicit operator ushort(System.Char8 value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static implicit operator uint (System.Char8 value) { throw null; }
+        public static implicit operator uint(System.Char8 value) { throw null; }
         [System.CLSCompliantAttribute(false)]
-        public static implicit operator ulong (System.Char8 value) { throw null; }
+        public static implicit operator ulong(System.Char8 value) { throw null; }
         public static bool operator !=(System.Char8 left, System.Char8 right) { throw null; }
         public static bool operator <(System.Char8 left, System.Char8 right) { throw null; }
         public static bool operator <=(System.Char8 left, System.Char8 right) { throw null; }
@@ -64,9 +64,10 @@ namespace System
         public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, int start) { throw null; }
         public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, int start, int length) { throw null; }
         public static System.ReadOnlyMemory<byte> AsMemoryBytes(this System.Utf8String text, System.Range range) { throw null; }
-        public static System.ReadOnlySpan<System.Char8> AsSpan(this System.Utf8String text) { throw null; }
-        public static System.ReadOnlySpan<System.Char8> AsSpan(this System.Utf8String text, int start) { throw null; }
-        public static System.ReadOnlySpan<System.Char8> AsSpan(this System.Utf8String text, int start, int length) { throw null; }
+        public static System.Text.Utf8Span AsSpan(this System.Utf8String? text) { throw null; }
+        public static System.Text.Utf8Span AsSpan(this System.Utf8String? text, int start) { throw null; }
+        public static System.Text.Utf8Span AsSpan(this System.Utf8String? text, int start, int length) { throw null; }
+        public static System.Utf8String ToUtf8String(this System.Text.Rune rune) { throw null; }
     }
     public sealed partial class Utf8String : System.IEquatable<System.Utf8String>
     {
@@ -82,11 +83,14 @@ namespace System
         public Utf8String(string value) { }
         public System.Char8 this[int index] { get { throw null; } }
         public int Length { get { throw null; } }
+        public static bool AreEquivalent(System.Utf8String? utf8Text, string? utf16Text) { throw null; }
+        public static bool AreEquivalent(System.Text.Utf8Span utf8Text, System.ReadOnlySpan<char> utf16Text) { throw null; }
+        public static bool AreEquivalent(System.ReadOnlySpan<byte> utf8Text, System.ReadOnlySpan<char> utf16Text) { throw null; }
         public bool Contains(char value) { throw null; }
         public bool Contains(System.Text.Rune value) { throw null; }
         public bool EndsWith(char value) { throw null; }
         public bool EndsWith(System.Text.Rune value) { throw null; }
-        public override bool Equals(object obj) { throw null; }
+        public override bool Equals(object? obj) { throw null; }
         public bool Equals(System.Utf8String value) { throw null; }
         public static bool Equals(System.Utf8String left, System.Utf8String right) { throw null; }
         public override int GetHashCode() { throw null; }
@@ -96,8 +100,9 @@ namespace System
         public int IndexOf(System.Text.Rune value) { throw null; }
         public static bool IsNullOrEmpty(System.Utf8String value) { throw null; }
         public static bool operator ==(System.Utf8String left, System.Utf8String right) { throw null; }
-        public static explicit operator System.ReadOnlySpan<byte> (System.Utf8String value) { throw null; }
-        public static implicit operator System.ReadOnlySpan<System.Char8> (System.Utf8String value) { throw null; }
+        public static explicit operator System.ReadOnlySpan<byte>(System.Utf8String value) { throw null; }
+        public static implicit operator System.ReadOnlySpan<System.Char8>(System.Utf8String value) { throw null; }
+        public static implicit operator System.Text.Utf8Span(System.Utf8String value) { throw null; }
         public static bool operator !=(System.Utf8String left, System.Utf8String right) { throw null; }
         [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
         public System.Utf8String Slice(int startIndex, int length) { throw null; }
@@ -109,15 +114,180 @@ namespace System
         public byte[] ToByteArray(int startIndex, int length) { throw null; }
         public override string ToString() { throw null; }
     }
+    [System.FlagsAttribute]
+    public enum Utf8StringSplitOptions
+    {
+        None = 0,
+        RemoveEmptyEntries = 1,
+        TrimEntries = 2
+    }
 }
 namespace System.Net.Http
 {
     public sealed partial class Utf8StringContent : System.Net.Http.HttpContent
     {
         public Utf8StringContent(System.Utf8String content) { }
-        public Utf8StringContent(System.Utf8String content, string mediaType) { }
+        public Utf8StringContent(System.Utf8String content, string? mediaType) { }
         protected override System.Threading.Tasks.Task<System.IO.Stream> CreateContentReadStreamAsync() { throw null; }
-        protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext context) { throw null; }
+        protected override System.Threading.Tasks.Task SerializeToStreamAsync(System.IO.Stream stream, System.Net.TransportContext? context) { throw null; }
         protected override bool TryComputeLength(out long length) { throw null; }
     }
 }
+namespace System.Text
+{
+    public readonly ref partial struct Utf8Span
+    {
+        private readonly object _dummy;
+        private readonly int _dummyPrimitive;
+        public Utf8Span(System.Utf8String? value) { throw null; }
+        public System.ReadOnlySpan<byte> Bytes { get { throw null; } }
+        public CharEnumerable Chars { get { throw null; } }
+        public static System.Text.Utf8Span Empty { get { throw null; } }
+        public bool IsEmpty { get { throw null; } }
+        public RuneEnumerable Runes { get { throw null; } }
+        public int CompareTo(System.Text.Utf8Span other) { throw null; }
+        public int CompareTo(System.Text.Utf8Span other, System.StringComparison comparison) { throw null; }
+        public bool Contains(char value) { throw null; }
+        public bool Contains(char value, System.StringComparison comparison) { throw null; }
+        public bool Contains(System.Text.Rune value) { throw null; }
+        public bool Contains(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+        public bool Contains(System.Text.Utf8Span value) { throw null; }
+        public bool Contains(System.Text.Utf8Span value, System.StringComparison comparison) { throw null; }
+        public bool EndsWith(char value) { throw null; }
+        public bool EndsWith(char value, System.StringComparison comparison) { throw null; }
+        public bool EndsWith(System.Text.Rune value) { throw null; }
+        public bool EndsWith(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+        public bool EndsWith(System.Text.Utf8Span value) { throw null; }
+        public bool EndsWith(System.Text.Utf8Span value, System.StringComparison comparison) { throw null; }
+        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+        [System.ObsoleteAttribute("Equals(object) on Utf8Span will always throw an exception. Use Equals(Utf8Span) or == instead.")]
+        public override bool Equals(object? obj) { throw null; }
+        public bool Equals(System.Text.Utf8Span other) { throw null; }
+        public bool Equals(System.Text.Utf8Span other, System.StringComparison comparison) { throw null; }
+        public static bool Equals(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; }
+        public static bool Equals(System.Text.Utf8Span left, System.Text.Utf8Span right, System.StringComparison comparison) { throw null; }
+        public override int GetHashCode() { throw null; }
+        public int GetHashCode(System.StringComparison comparison) { throw null; }
+        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+        public ref readonly byte GetPinnableReference() { throw null; }
+        public bool IsAscii() { throw null; }
+        public bool IsEmptyOrWhiteSpace() { throw null; }
+        public bool IsNormalized(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; }
+        public System.Utf8String Normalize(System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; }
+        public int Normalize(System.Span<byte> destination, System.Text.NormalizationForm normalizationForm = System.Text.NormalizationForm.FormC) { throw null; }
+        public static bool operator !=(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; }
+        public static bool operator ==(System.Text.Utf8Span left, System.Text.Utf8Span right) { throw null; }
+        public System.Text.Utf8Span this[System.Range range] { get { throw null; } }
+        public SplitResult Split(char separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+        public SplitResult Split(System.Text.Rune separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+        public SplitResult Split(System.Text.Utf8Span separator, System.Utf8StringSplitOptions options = System.Utf8StringSplitOptions.None) { throw null; }
+        public SplitOnResult SplitOn(char separator) { throw null; }
+        public SplitOnResult SplitOn(char separator, System.StringComparison comparisonType) { throw null; }
+        public SplitOnResult SplitOn(System.Text.Rune separator) { throw null; }
+        public SplitOnResult SplitOn(System.Text.Rune separator, System.StringComparison comparisonType) { throw null; }
+        public SplitOnResult SplitOn(System.Text.Utf8Span separator) { throw null; }
+        public SplitOnResult SplitOn(System.Text.Utf8Span separator, System.StringComparison comparisonType) { throw null; }
+        public SplitOnResult SplitOnLast(char separator) { throw null; }
+        public SplitOnResult SplitOnLast(char separator, System.StringComparison comparisonType) { throw null; }
+        public SplitOnResult SplitOnLast(System.Text.Rune separator) { throw null; }
+        public SplitOnResult SplitOnLast(System.Text.Rune separator, System.StringComparison comparisonType) { throw null; }
+        public SplitOnResult SplitOnLast(System.Text.Utf8Span separator) { throw null; }
+        public SplitOnResult SplitOnLast(System.Text.Utf8Span separator, System.StringComparison comparisonType) { throw null; }
+        public bool StartsWith(char value) { throw null; }
+        public bool StartsWith(char value, System.StringComparison comparison) { throw null; }
+        public bool StartsWith(System.Text.Rune value) { throw null; }
+        public bool StartsWith(System.Text.Rune value, System.StringComparison comparison) { throw null; }
+        public bool StartsWith(System.Text.Utf8Span value) { throw null; }
+        public bool StartsWith(System.Text.Utf8Span value, System.StringComparison comparison) { throw null; }
+        public System.Text.Utf8Span Trim() { throw null; }
+        public System.Text.Utf8Span TrimEnd() { throw null; }
+        public System.Text.Utf8Span TrimStart() { throw null; }
+        public char[] ToCharArray() { throw null; }
+        public int ToChars(System.Span<char> destination) { throw null; }
+        public System.Utf8String ToLower(System.Globalization.CultureInfo culture) { throw null; }
+        public int ToLower(System.Span<byte> destination, System.Globalization.CultureInfo culture) { throw null; }
+        public System.Utf8String ToLowerInvariant() { throw null; }
+        public int ToLowerInvariant(System.Span<byte> destination) { throw null; }
+        public override string ToString() { throw null; }
+        public System.Utf8String ToUpper(System.Globalization.CultureInfo culture) { throw null; }
+        public int ToUpper(System.Span<byte> destination, System.Globalization.CultureInfo culture) { throw null; }
+        public System.Utf8String ToUpperInvariant() { throw null; }
+        public int ToUpperInvariant(System.Span<byte> destination) { throw null; }
+        public System.Utf8String ToUtf8String() { throw null; }
+        public bool TryFind(char value, out System.Range range) { throw null; }
+        public bool TryFind(char value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public bool TryFind(System.Text.Rune value, out System.Range range) { throw null; }
+        public bool TryFind(System.Text.Rune value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public bool TryFind(System.Text.Utf8Span value, out System.Range range) { throw null; }
+        public bool TryFind(System.Text.Utf8Span value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public bool TryFindLast(char value, out System.Range range) { throw null; }
+        public bool TryFindLast(char value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public bool TryFindLast(System.Text.Rune value, out System.Range range) { throw null; }
+        public bool TryFindLast(System.Text.Rune value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public bool TryFindLast(System.Text.Utf8Span value, out System.Range range) { throw null; }
+        public bool TryFindLast(System.Text.Utf8Span value, System.StringComparison comparisonType, out System.Range range) { throw null; }
+        public static System.Text.Utf8Span UnsafeCreateWithoutValidation(System.ReadOnlySpan<byte> buffer) { throw null; }
+        public readonly ref struct CharEnumerable
+        {
+            private readonly object _dummy;
+            private readonly int _dummyPrimitive;
+            public Enumerator GetEnumerator() { throw null; }
+            public ref struct Enumerator
+            {
+                private object _dummy;
+                private int _dummyPrimitive;
+                public char Current { get { throw null; } }
+                public bool MoveNext() { throw null; }
+            }
+        }
+        public readonly ref struct RuneEnumerable
+        {
+            private readonly object _dummy;
+            private readonly int _dummyPrimitive;
+            public Enumerator GetEnumerator() { throw null; }
+            public ref struct Enumerator
+            {
+                private object _dummy;
+                private int _dummyPrimitive;
+                public System.Text.Rune Current { get { throw null; } }
+                public bool MoveNext() { throw null; }
+            }
+        }
+        public readonly ref struct SplitResult
+        {
+            private readonly object _dummy;
+            private readonly int _dummyPrimitive;
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3, out System.Text.Utf8Span item4) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3, out System.Text.Utf8Span item4, out System.Text.Utf8Span item5) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3, out System.Text.Utf8Span item4, out System.Text.Utf8Span item5, out System.Text.Utf8Span item6) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3, out System.Text.Utf8Span item4, out System.Text.Utf8Span item5, out System.Text.Utf8Span item6, out System.Text.Utf8Span item7) { throw null; }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span item1, out System.Text.Utf8Span item2, out System.Text.Utf8Span item3, out System.Text.Utf8Span item4, out System.Text.Utf8Span item5, out System.Text.Utf8Span item6, out System.Text.Utf8Span item7, out System.Text.Utf8Span item8) { throw null; }
+            public Enumerator GetEnumerator() { throw null; }
+            public ref struct Enumerator
+            {
+                private readonly object _dummy;
+                private readonly int _dummyPrimitive;
+                public System.Text.Utf8Span Current { get { throw null; } }
+                public bool MoveNext() { throw null; }
+            }
+        }
+        public readonly ref struct SplitOnResult
+        {
+            private readonly object _dummy;
+            private readonly int _dummyPrimitive;
+            public Utf8Span After { get { throw null; } }
+            public Utf8Span Before { get { throw null; } }
+            [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+            public void Deconstruct(out System.Text.Utf8Span before, out System.Text.Utf8Span after) { throw null; }
+        }
+    }
+}
index 5233d28..dfb8a35 100644 (file)
@@ -1,7 +1,11 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <ProjectGuid>{7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0}</ProjectGuid>
+    <!-- disable warnings about obsolete APIs -->
+    <NoWarn>$(NoWarn);0809;0618</NoWarn>
     <Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
+    <Nullable>enable</Nullable>
   </PropertyGroup>
   <ItemGroup Condition="'$(IsPrerelease)' != 'false'">
     <Compile Include="System.Utf8String.Experimental.cs" />
index c2ffa23..5a339db 100644 (file)
@@ -25,14 +25,14 @@ namespace System.IO
 
         public override bool CanWrite => false;
 
-        public override long Length => _content.Length;
+        public override long Length => _content.AsBytes().Length;
 
         public override long Position
         {
             get => _position;
             set
             {
-                if ((ulong)value > (uint)_content.Length)
+                if ((ulong)value > (uint)_content.AsBytes().Length)
                 {
                     throw new ArgumentOutOfRangeException(nameof(value));
                 }
@@ -84,13 +84,13 @@ namespace System.IO
         public override int ReadByte()
         {
             int position = _position;
-            if ((uint)position >= (uint)_content.Length)
+            if ((uint)position >= (uint)_content.AsBytes().Length)
             {
                 return -1;
             }
 
             _position++;
-            return _content[position];
+            return _content.AsBytes()[position];
         }
 
         public override long Seek(long offset, SeekOrigin origin)
@@ -103,13 +103,13 @@ namespace System.IO
                     offset += _position;
                     break;
                 case SeekOrigin.End:
-                    offset += _content.Length;
+                    offset += _content.AsBytes().Length;
                     break;
                 default:
                     throw new ArgumentOutOfRangeException(nameof(origin));
             }
 
-            if ((ulong)offset > (uint)_content.Length)
+            if ((ulong)offset > (uint)_content.AsBytes().Length)
             {
                 throw new ArgumentOutOfRangeException(nameof(offset));
             }
index 18b36ee..2c350f0 100644 (file)
@@ -48,7 +48,7 @@ namespace System.Net.Http
 
         protected override bool TryComputeLength(out long length)
         {
-            length = _content.Length;
+            length = _content.AsBytes().Length;
             return true;
         }
     }
index b6fb150..deb16c3 100644 (file)
@@ -4,6 +4,7 @@
     <IncludePartialFacadeTests>true</IncludePartialFacadeTests>
     <Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
     <TestRuntime>true</TestRuntime>
+    <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
     <RootNamespace>System</RootNamespace>
   </PropertyGroup>
   <PropertyGroup>
     <Compile Include="System\Net\Http\Utf8StringContentTests.cs" />
     <Compile Include="System\ReflectionTests.cs" />
     <Compile Include="System\Utf8ExtensionsTests.cs" />
+    <Compile Include="System\BoundedUtf8Span.cs" />
+    <Compile Include="System\RangeEqualityComparer.cs" />
+    <Compile Include="System\Utf8SpanTests.cs" />
+    <Compile Include="System\Utf8SpanTests.Comparison.cs" />
+    <Compile Include="System\Utf8SpanTests.Conversion.cs" />
+    <Compile Include="System\Utf8SpanTests.Ctor.cs" />
+    <Compile Include="System\Utf8SpanTests.Enumeration.cs" />
+    <Compile Include="System\Utf8SpanTests.Manipulation.cs" />
+    <Compile Include="System\Utf8SpanTests.Manipulation.TestData.cs" />
+    <Compile Include="System\Utf8SpanTests.Searching.cs" />
+    <Compile Include="System\Utf8SpanTests.Searching.TestData.cs" />
+    <Compile Include="System\Utf8SpanTests.TestData.cs" />
     <Compile Include="System\Utf8StringTests.cs" />
     <Compile Include="System\Utf8StringTests.Ctor.cs" />
     <Compile Include="System\Utf8StringTests.Searching.cs" />
     <Compile Include="System\Utf8StringTests.Substring.cs" />
-    <Compile Include="System\Utf8TestUtilities.cs" />
     <Compile Include="Xunit\SpanAssert.cs" />
+    <Compile Include="System\Utf8TestUtilities.cs" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/BoundedUtf8Span.cs b/src/libraries/System.Utf8String.Experimental/tests/System/BoundedUtf8Span.cs
new file mode 100644 (file)
index 0000000..d8a1c46
--- /dev/null
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Text;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Tests
+{
+    /// <summary>
+    /// Allows creating <see cref="Utf8Span"/> instances that wrap <see cref="BoundedMemory{Byte}"/>.
+    /// Useful for ensuring an API under test doesn't read past the end of the span.
+    /// </summary>
+    public sealed class BoundedUtf8Span : IDisposable
+    {
+        private readonly BoundedMemory<byte> _boundedMemory;
+
+        public BoundedUtf8Span(ReadOnlySpan<char> utf16Data, PoisonPagePlacement placement = PoisonPagePlacement.After)
+            : this(u8(utf16Data.ToString()).AsBytes(), placement)
+        {
+        }
+
+        public BoundedUtf8Span(ReadOnlySpan<byte> utf8Data, PoisonPagePlacement placement = PoisonPagePlacement.After)
+        {
+            _boundedMemory = BoundedMemory.AllocateFromExistingData(utf8Data, placement);
+        }
+
+        public Utf8Span Span => Utf8Span.UnsafeCreateWithoutValidation(_boundedMemory.Span);
+
+        public void Dispose()
+        {
+            _boundedMemory.Dispose();
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/RangeEqualityComparer.cs b/src/libraries/System.Utf8String.Experimental/tests/System/RangeEqualityComparer.cs
new file mode 100644 (file)
index 0000000..8cc3938
--- /dev/null
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Tests
+{
+    /// <summary>
+    /// Given a fixed length, compares two <see cref="Range"/> instances for equality.
+    /// </summary>
+    public sealed class RangeEqualityComparer : IEqualityComparer<Range>
+    {
+        private int _length;
+
+        public RangeEqualityComparer(int length)
+        {
+            Assert.True(length >= 0);
+
+            _length = length;
+        }
+
+        public bool Equals(Range x, Range y)
+        {
+            (int offsetX, int lengthX) = x.GetOffsetAndLength(_length);
+            (int offsetY, int lengthY) = y.GetOffsetAndLength(_length);
+
+            return offsetX == offsetY && lengthX == lengthY;
+        }
+
+        public int GetHashCode(Range obj)
+        {
+            (int offset, int length) = obj.GetOffsetAndLength(_length);
+            return HashCode.Combine(offset, length);
+        }
+    }
+}
index a3c2182..604b02e 100644 (file)
@@ -165,19 +165,20 @@ namespace System.Tests
         [Fact]
         public void AsSpan_FromUtf8String()
         {
-            Assert.True(default(ReadOnlySpan<Char8>) == ((Utf8String)null).AsSpan());
+            Assert.True(((Utf8String)null).AsSpan().Bytes == default); // referential equality check
 
             Utf8String theString = u8("Hello");
-            Assert.True(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<byte, Char8>(ref Unsafe.AsRef(in theString.GetPinnableReference())), 5) == theString.AsSpan());
+
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in theString.GetPinnableReference()), ref Unsafe.AsRef(in theString.AsSpan().GetPinnableReference())));
+            Assert.Equal(5, theString.AsSpan().Bytes.Length);
         }
 
         [Fact]
         public void AsSpan_FromUtf8String_WithStart()
         {
-            Assert.True(default(ReadOnlySpan<Char8>) == ((Utf8String)null).AsSpan(0));
+            Assert.True(((Utf8String)null).AsSpan(0).Bytes == default); // referential equality check
             Assert.True(u8("Hello").AsSpan(5).IsEmpty);
-
-            SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l', (Char8)'o' }, u8("Hello").AsSpan(1));
+            Assert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l', (byte)'o' }, u8("Hello").AsSpan(1).Bytes.ToArray());
         }
 
         [Fact]
@@ -191,10 +192,9 @@ namespace System.Tests
         [Fact]
         public void AsSpan_FromUtf8String_WithStartAndLength()
         {
-            Assert.True(default(ReadOnlySpan<Char8>) == ((Utf8String)null).AsSpan(0, 0));
+            Assert.True(((Utf8String)null).AsSpan(0, 0).Bytes == default); // referential equality check
             Assert.True(u8("Hello").AsSpan(5, 0).IsEmpty);
-
-            SpanAssert.Equal(new Char8[] { (Char8)'e', (Char8)'l', (Char8)'l' }, u8("Hello").AsSpan(1, 3));
+            Assert.Equal(new byte[] { (byte)'e', (byte)'l', (byte)'l' }, u8("Hello").AsSpan(1, 3).Bytes.ToArray());
         }
 
         [Fact]
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Comparison.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Comparison.cs
new file mode 100644 (file)
index 0000000..058ff7f
--- /dev/null
@@ -0,0 +1,146 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Tests;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+namespace System.Text.Tests
+{
+    public partial class Utf8SpanTests
+    {
+        [Fact]
+        public static void Equals_Object_ThrowsNotSupported()
+        {
+            Utf8Span span = Utf8Span.Empty;
+
+            Assert.Throws<NotSupportedException>(() => Utf8Span.Empty.Equals((object)null));
+            Assert.Throws<NotSupportedException>(() => Utf8Span.Empty.Equals(new object()));
+        }
+
+        [Fact]
+        public static void Equals_Ordinal()
+        {
+            // First make sure referential equality passes
+
+            Utf8Span span1 = u8("Hello!");
+            Utf8Span span2 = span1;
+            AssertEqualOrdinal(span1, span2);
+
+            // Now make sure deep equality passes
+
+            span2 = Utf8Span.UnsafeCreateWithoutValidation(Encoding.UTF8.GetBytes("Hello!"));
+            AssertEqualOrdinal(span1, span2);
+
+            // Now mutate one of the inputs and make sure they're inequal
+
+            span2 = u8("Bello!");
+            AssertNotEqualOrdinal(span1, span2);
+
+            // Finally, make sure null / null and null / empty are treated as the same
+
+            AssertEqualOrdinal(Utf8Span.Empty, Utf8Span.Empty);
+            AssertEqualOrdinal(Utf8Span.Empty, u8(""));
+        }
+
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [InlineData(null, null, StringComparison.OrdinalIgnoreCase, null, true)]
+        [InlineData("encyclopaedia", "encyclopædia", StringComparison.OrdinalIgnoreCase, null, false)]
+        [InlineData("encyclopaedia", "encyclopædia", StringComparison.InvariantCulture, null, true)]
+        [InlineData("encyclopaedia", "ENCYCLOPÆDIA", StringComparison.InvariantCulture, null, false)]
+        [InlineData("encyclopaedia", "encyclopædia", StringComparison.InvariantCultureIgnoreCase, null, true)]
+        [InlineData("encyclopaedia", "ENCYCLOPÆDIA", StringComparison.InvariantCultureIgnoreCase, null, true)]
+        [InlineData("Weiß", "WEISS", StringComparison.OrdinalIgnoreCase, null, false)]
+        [InlineData("Weiß", "WEISS", StringComparison.InvariantCulture, null, false)]
+        [InlineData("Weiß", "WEISS", StringComparison.InvariantCultureIgnoreCase, null, true)]
+        [InlineData("Weiß", "WEISS", StringComparison.CurrentCulture, "de-DE", false)]
+        [InlineData("Weiß", "WEISS", StringComparison.CurrentCultureIgnoreCase, "de-DE", true)]
+        [InlineData("γένεσις", "ΓΈΝΕΣΙΣ", StringComparison.InvariantCultureIgnoreCase, null, true)]
+        [InlineData("ıI", "iI", StringComparison.CurrentCulture, "tr-TR", false)]
+        [InlineData("ıI", "iI", StringComparison.CurrentCultureIgnoreCase, "tr-TR", false)]
+        [InlineData("İI", "iI", StringComparison.CurrentCultureIgnoreCase, "tr-TR", true)]
+        public static void Equals_NonOrdinal(string str1, string str2, StringComparison comparison, string culture, bool shouldCompareAsEqual)
+        {
+            Func<string, string, string, string, string, int> action = (str1, str2, comparison, culture, shouldCompareAsEqual) =>
+            {
+                if (culture != null)
+                {
+                    CultureInfo.CurrentCulture = new CultureInfo(culture);
+                }
+
+                using BoundedUtf8Span boundedSpan1 = new BoundedUtf8Span(str1);
+                using BoundedUtf8Span boundedSpan2 = new BoundedUtf8Span(str2);
+
+                Utf8Span span1 = boundedSpan1.Span;
+                Utf8Span span2 = boundedSpan2.Span;
+
+                StringComparison comparisonType = Enum.Parse<StringComparison>(comparison);
+                bool expected = bool.Parse(shouldCompareAsEqual);
+
+                Assert.Equal(expected, span1.Equals(span2, comparisonType));
+                Assert.Equal(expected, span2.Equals(span1, comparisonType));
+                Assert.Equal(expected, Utf8Span.Equals(span1, span2, comparisonType));
+                Assert.Equal(expected, Utf8Span.Equals(span2, span1, comparisonType));
+
+                return RemoteExecutor.SuccessExitCode;
+            };
+
+            if (culture != null)
+            {
+                // need to apply a culture to the current thread
+                RemoteExecutor.Invoke(action, str1, str2, comparison.ToString(), culture, shouldCompareAsEqual.ToString()).Dispose();
+            }
+            else
+            {
+                action(str1, str2, comparison.ToString(), culture, shouldCompareAsEqual.ToString());
+            }
+        }
+
+        private static void AssertEqualOrdinal(Utf8Span span1, Utf8Span span2)
+        {
+            Assert.True(span1.Equals(span2));
+            Assert.True(span2.Equals(span1));
+
+            Assert.True(span1.Equals(span2, StringComparison.Ordinal));
+            Assert.True(span2.Equals(span1, StringComparison.Ordinal));
+
+            Assert.True(Utf8Span.Equals(span1, span2));
+            Assert.True(Utf8Span.Equals(span2, span1));
+
+            Assert.True(Utf8Span.Equals(span1, span2, StringComparison.Ordinal));
+            Assert.True(Utf8Span.Equals(span2, span1, StringComparison.Ordinal));
+
+            Assert.True(span1 == span2);
+            Assert.True(span2 == span1);
+
+            Assert.False(span1 != span2);
+            Assert.False(span2 != span1);
+        }
+
+        private static void AssertNotEqualOrdinal(Utf8Span span1, Utf8Span span2)
+        {
+            Assert.False(span1.Equals(span2));
+            Assert.False(span2.Equals(span1));
+
+            Assert.False(span1.Equals(span2, StringComparison.Ordinal));
+            Assert.False(span2.Equals(span1, StringComparison.Ordinal));
+
+            Assert.False(Utf8Span.Equals(span1, span2));
+            Assert.False(Utf8Span.Equals(span2, span1));
+
+            Assert.False(Utf8Span.Equals(span1, span2, StringComparison.Ordinal));
+            Assert.False(Utf8Span.Equals(span2, span1, StringComparison.Ordinal));
+
+            Assert.False(span1 == span2);
+            Assert.False(span2 == span1);
+
+            Assert.True(span1 != span2);
+            Assert.True(span2 != span1);
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Conversion.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Conversion.cs
new file mode 100644 (file)
index 0000000..01721a4
--- /dev/null
@@ -0,0 +1,242 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Globalization;
+using System.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public partial class Utf8SpanTests
+    {
+        [Theory]
+        [MemberData(nameof(NormalizationData))]
+        public static void Normalize(string utf16Source, string utf16Expected, NormalizationForm normalizationForm)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(utf16Source);
+            Utf8Span utf8Source = boundedSpan.Span;
+
+            // Quick IsNormalized tests
+
+            Assert.Equal(utf16Source == utf16Expected, utf8Source.IsNormalized(normalizationForm));
+
+            // Normalize and return new Utf8String instances
+
+            ustring utf8Normalized = utf8Source.Normalize(normalizationForm);
+            Assert.True(ustring.AreEquivalent(utf8Normalized, utf16Expected));
+
+            // Normalize to byte arrays which are too small, expect -1 (failure)
+
+            Assert.Equal(-1, utf8Source.Normalize(new byte[utf8Normalized.GetByteLength() - 1], normalizationForm));
+
+            // Normalize to byte arrays which are the correct length, expect success,
+            // then compare buffer contents for ordinal equality.
+
+            foreach (int bufferLength in new int[] { utf8Normalized.GetByteLength() /* just right */, utf8Normalized.GetByteLength() + 1 /* with extra room */})
+            {
+                byte[] dest = new byte[bufferLength];
+                Assert.Equal(utf8Normalized.GetByteLength(), utf8Source.Normalize(dest, normalizationForm));
+                Utf8Span normalizedSpan = Utf8Span.UnsafeCreateWithoutValidation(dest[..utf8Normalized.GetByteLength()]);
+                Assert.True(utf8Normalized.AsSpan() == normalizedSpan); // ordinal equality
+                Assert.True(normalizedSpan.IsNormalized(normalizationForm));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(CaseConversionData))]
+        public static void ToLower(string testData)
+        {
+            static void RunTest(string testData, string expected, CultureInfo culture)
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(testData);
+                Utf8Span inputSpan = boundedSpan.Span;
+
+                // First try the allocating APIs
+
+                ustring expectedUtf8 = u8(expected) ?? ustring.Empty;
+                ustring actualUtf8;
+
+                if (culture is null)
+                {
+                    actualUtf8 = inputSpan.ToLowerInvariant();
+                }
+                else
+                {
+                    actualUtf8 = inputSpan.ToLower(culture);
+                }
+
+                Assert.Equal(expectedUtf8, actualUtf8);
+
+                // Next, try the non-allocating APIs with too small a buffer
+
+                if (expectedUtf8.GetByteLength() > 0)
+                {
+                    byte[] bufferTooSmall = new byte[expectedUtf8.GetByteLength() - 1];
+
+                    if (culture is null)
+                    {
+                        Assert.Equal(-1, inputSpan.ToLowerInvariant(bufferTooSmall));
+                    }
+                    else
+                    {
+                        Assert.Equal(-1, inputSpan.ToLower(bufferTooSmall, culture));
+                    }
+                }
+
+                // Then the non-allocating APIs with a properly sized buffer
+
+                foreach (int bufferSize in new[] { expectedUtf8.GetByteLength(), expectedUtf8.GetByteLength() + 1 })
+                {
+                    byte[] buffer = new byte[expectedUtf8.GetByteLength()];
+
+                    if (culture is null)
+                    {
+                        Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToLowerInvariant(buffer));
+                    }
+                    else
+                    {
+                        Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToLower(buffer, culture));
+                    }
+
+                    Assert.True(expectedUtf8.AsBytes().SequenceEqual(buffer));
+                }
+            }
+
+            RunTest(testData, testData?.ToLowerInvariant(), null);
+            RunTest(testData, testData?.ToLower(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
+            RunTest(testData, testData?.ToLower(CultureInfo.GetCultureInfo("en-US")), CultureInfo.GetCultureInfo("en-US"));
+            RunTest(testData, testData?.ToLower(CultureInfo.GetCultureInfo("tr-TR")), CultureInfo.GetCultureInfo("tr-TR"));
+        }
+
+        [Theory]
+        [MemberData(nameof(CaseConversionData))]
+        public static void ToUpper(string testData)
+        {
+            static void RunTest(string testData, string expected, CultureInfo culture)
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(testData);
+                Utf8Span inputSpan = boundedSpan.Span;
+
+                // First try the allocating APIs
+
+                ustring expectedUtf8 = u8(expected) ?? ustring.Empty;
+                ustring actualUtf8;
+
+                if (culture is null)
+                {
+                    actualUtf8 = inputSpan.ToUpperInvariant();
+                }
+                else
+                {
+                    actualUtf8 = inputSpan.ToUpper(culture);
+                }
+
+                Assert.Equal(expectedUtf8, actualUtf8);
+
+                // Next, try the non-allocating APIs with too small a buffer
+
+                if (expectedUtf8.GetByteLength() > 0)
+                {
+                    byte[] bufferTooSmall = new byte[expectedUtf8.GetByteLength() - 1];
+
+                    if (culture is null)
+                    {
+                        Assert.Equal(-1, inputSpan.ToUpperInvariant(bufferTooSmall));
+                    }
+                    else
+                    {
+                        Assert.Equal(-1, inputSpan.ToUpper(bufferTooSmall, culture));
+                    }
+                }
+
+                // Then the non-allocating APIs with a properly sized buffer
+
+                foreach (int bufferSize in new[] { expectedUtf8.GetByteLength(), expectedUtf8.GetByteLength() + 1 })
+                {
+                    byte[] buffer = new byte[expectedUtf8.GetByteLength()];
+
+                    if (culture is null)
+                    {
+                        Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToUpperInvariant(buffer));
+                    }
+                    else
+                    {
+                        Assert.Equal(expectedUtf8.GetByteLength(), inputSpan.ToUpper(buffer, culture));
+                    }
+
+                    Assert.True(expectedUtf8.AsBytes().SequenceEqual(buffer));
+                }
+            }
+
+            RunTest(testData, testData?.ToUpperInvariant(), null);
+            RunTest(testData, testData?.ToUpper(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
+            RunTest(testData, testData?.ToUpper(CultureInfo.GetCultureInfo("en-US")), CultureInfo.GetCultureInfo("en-US"));
+            RunTest(testData, testData?.ToUpper(CultureInfo.GetCultureInfo("tr-TR")), CultureInfo.GetCultureInfo("tr-TR"));
+        }
+
+        public static IEnumerable<object[]> CaseConversionData()
+        {
+            string[] testCases = new string[]
+            {
+                null,
+                string.Empty,
+                "Hello",
+                "iı", // dotted and dotless I
+                "İI", // dotted and dotless I
+            };
+
+            foreach (string testCase in testCases)
+            {
+                yield return new object[] { testCase };
+            }
+        }
+
+        public static IEnumerable<object[]> NormalizationData()
+        {
+            // These test cases are from the Unicode Standard Annex #15, Figure 6
+            // https://unicode.org/reports/tr15/
+
+            var testCases = new[]
+            {
+                new
+                {
+                    Source = "\ufb01", // "fi" (LATIN SMALL LIGATURE FI)
+                    NFD = "\ufb01", // same as source
+                    NFC = "\ufb01", // same as source
+                    NFKD = "fi", // compatibility decomposed into ASCII chars
+                    NFKC = "fi", // compatibility decomposed into ASCII chars
+                },
+                new
+                {
+                    Source = "2\u2075", // "2⁵" (SUPERSCRIPT FIVE)
+                    NFD = "2\u2075", // same as source
+                    NFC = "2\u2075", // same as source
+                    NFKD = "25", // compatibility decomposed into ASCII chars
+                    NFKC = "25", // compatibility decomposed into ASCII chars
+                },
+                new
+                {
+                    Source = "\u1e9b\u0323", // 'ẛ' (LATIN SMALL LETTER LONG S WITH DOT ABOVE) + COMBINING DOT BELOW
+                    NFD = "\u017f\u0323\u0307", // 'ſ' (LATIN SMALL LETTER LONG S) + COMBINING DOT BELOW + COMBINING DOT ABOVE
+                    NFC = "\u1e9b\u0323", // same as source
+                    NFKD = "s\u0323\u0307", // ASCII 's' + COMBINING DOT BELOW + COMBINING DOT ABOVE
+                    NFKC = "\u1e69", // "ṩ" (LATIN SMALL LETTER S WITH DOT BELOW AND DOT ABOVE)
+                }
+            };
+
+            foreach (var testCase in testCases)
+            {
+                yield return new object[] { testCase.Source, testCase.NFD, NormalizationForm.FormD };
+                yield return new object[] { testCase.Source, testCase.NFC, NormalizationForm.FormC };
+                yield return new object[] { testCase.Source, testCase.NFKD, NormalizationForm.FormKD };
+                yield return new object[] { testCase.Source, testCase.NFKC, NormalizationForm.FormKC };
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Ctor.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Ctor.cs
new file mode 100644 (file)
index 0000000..725a775
--- /dev/null
@@ -0,0 +1,135 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public unsafe partial class Utf8SpanTests
+    {
+        [Fact]
+        public static void Ctor_EmptyUtf8String()
+        {
+            // Arrange
+
+            ustring str = ustring.Empty;
+
+            // Act
+
+            Utf8Span span = new Utf8Span(str);
+
+            // Assert
+            // GetPinnableReference should be 'null' to match behavior of empty ROS<byte>.GetPinnableReference();
+
+            Assert.True(span.IsEmpty);
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(0, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void Ctor_NonEmptyUtf8String()
+        {
+            // Arrange
+
+            ustring str = u8("Hello!");
+
+            // Act
+
+            Utf8Span span = new Utf8Span(str);
+
+            // Assert
+
+            Assert.False(span.IsEmpty);
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in str.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(6, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void Ctor_NullUtf8String()
+        {
+            // Arrange
+
+            ustring str = null;
+
+            // Act
+
+            Utf8Span span = new Utf8Span(str);
+
+            // Assert
+            // GetPinnableReference should be 'null' to match behavior of empty ROS<byte>.GetPinnableReference();
+
+            Assert.True(span.IsEmpty);
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(0, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void Ctor_UnsafeFromByteSpan_NonEmpty()
+        {
+            // Arrange
+
+            ReadOnlySpan<byte> original = new byte[] { 1, 2, 3, 4, 5 };
+
+            // Act
+
+            Utf8Span span = Utf8Span.UnsafeCreateWithoutValidation(original);
+
+            // Assert
+
+            Assert.False(span.IsEmpty);
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in original.GetPinnableReference()), ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in original.GetPinnableReference()), ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(5, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void Ctor_UnsafeFromByteSpan_NonNullEmptyArray()
+        {
+            // Arrange
+
+            ReadOnlySpan<byte> original = new byte[0];
+
+            // Act
+
+            Utf8Span span = Utf8Span.UnsafeCreateWithoutValidation(original);
+
+            // Assert
+            // GetPinnableReference should be 'null' to match behavior of empty ROS<byte>.GetPinnableReference();
+
+            Assert.True(span.IsEmpty);
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.True(Unsafe.AreSame(ref MemoryMarshal.GetReference(original), ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(0, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void Ctor_UnsafeFromByteSpan_Default()
+        {
+            // Arrange
+
+            ReadOnlySpan<byte> original = default;
+
+            // Act
+
+            Utf8Span span = Utf8Span.UnsafeCreateWithoutValidation(original);
+
+            // Assert
+            // GetPinnableReference should be 'null' to match behavior of empty ROS<byte>.GetPinnableReference();
+
+            Assert.True(span.IsEmpty);
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(0, span.Bytes.Length);
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Enumeration.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Enumeration.cs
new file mode 100644 (file)
index 0000000..64b85a9
--- /dev/null
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Tests;
+using Xunit;
+
+namespace System.Text.Tests
+{
+    public unsafe partial class Utf8SpanTests
+    {
+        [Fact]
+        public static void CharsProperty_FromData()
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
+            Utf8Span span = boundedSpan.Span;
+
+            var charsEnumerator = span.Chars.GetEnumerator();
+
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00000012', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00000123', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00001234', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\uDBC4', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\uDE34', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00000012', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00000123', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\U00001234', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\uDBC4', charsEnumerator.Current);
+            Assert.True(charsEnumerator.MoveNext());
+            Assert.Equal('\uDE34', charsEnumerator.Current);
+            Assert.False(charsEnumerator.MoveNext());
+        }
+
+        [Fact]
+        public static void CharsProperty_FromEmpty()
+        {
+            Assert.False(Utf8Span.Empty.Chars.GetEnumerator().MoveNext());
+        }
+
+        [Fact]
+        public static void RunesProperty_FromEmpty()
+        {
+            Assert.False(Utf8Span.Empty.Runes.GetEnumerator().MoveNext());
+        }
+
+        [Fact]
+        public static void RunesProperty_FromData()
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("\U00000012\U00000123\U00001234\U00101234\U00000012\U00000123\U00001234\U00101234");
+            Utf8Span span = boundedSpan.Span;
+
+            var runesEnumerator = span.Runes.GetEnumerator();
+
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x0012), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x0123), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x1234), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x101234), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x0012), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x0123), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x1234), runesEnumerator.Current);
+            Assert.True(runesEnumerator.MoveNext());
+            Assert.Equal(new Rune(0x101234), runesEnumerator.Current);
+            Assert.False(runesEnumerator.MoveNext());
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.TestData.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.TestData.cs
new file mode 100644 (file)
index 0000000..95503bc
--- /dev/null
@@ -0,0 +1,130 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Tests;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public partial class Utf8SpanTests
+    {
+        public static IEnumerable<object[]> SplitData_CharSeparator()
+        {
+            foreach (SplitTestData entry in SplitData_All())
+            {
+                if (!TryParseSearchTermAsChar(entry.SearchTerm, out char searchChar))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchChar,
+                    entry.ExpectedRanges
+                };
+            }
+        }
+
+        public static IEnumerable<object[]> SplitData_RuneSeparator()
+        {
+            foreach (SplitTestData entry in SplitData_All())
+            {
+                if (!TryParseSearchTermAsRune(entry.SearchTerm, out Rune searchRune))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchRune,
+                    entry.ExpectedRanges
+                };
+            }
+        }
+
+        public static IEnumerable<object[]> SplitData_Utf8SpanSeparator()
+        {
+            foreach (SplitTestData entry in SplitData_All())
+            {
+                if (!TryParseSearchTermAsUtf8String(entry.SearchTerm, out ustring searchTerm))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchTerm,
+                    entry.ExpectedRanges
+                };
+            }
+        }
+
+        private static IEnumerable<SplitTestData> SplitData_All()
+        {
+            SplitTestData[] testDataEntries = new SplitTestData[]
+            {
+                new SplitTestData
+                {
+                    // Empty source, searching for anything results in no match
+                    Source = null,
+                    SearchTerm = '\0',
+                    ExpectedRanges = new[] { 0..0 }
+                },
+                new SplitTestData
+                {
+                    // If no match, then return original span
+                    Source = u8("Hello"),
+                    SearchTerm = 'x',
+                    ExpectedRanges = new[] { Range.All }
+                },
+                new SplitTestData
+                {
+                    // Match returns multiple spans (some may be empty)
+                    Source = u8("Hello"),
+                    SearchTerm = 'l',
+                    ExpectedRanges = new[] { 0..2, 3..3, 4..5 }
+                },
+                new SplitTestData
+                {
+                    // Match returns multiple spans (non-empty)
+                    Source = u8("Hello"),
+                    SearchTerm = "ell",
+                    ExpectedRanges = new[] { 0..1, ^1.. }
+                },
+                new SplitTestData
+                {
+                    // Match returns multiple spans (non-empty, with whitespace)
+                    Source = u8("aax aaa xxax \u2028\u2029"), // includes LS, PS as whitespace
+                    SearchTerm = 'x',
+                    ExpectedRanges = new[] { 0..2, 3..8, 9..9, 10..11, 12.. }
+                },
+                new SplitTestData
+                {
+                    // Matching on U+1F600 GRINNING FACE (with whitespace)
+                    Source = u8("x \U0001F600 y"),
+                    SearchTerm = new Rune(0x1F600),
+                    ExpectedRanges = new[] { 0..2, ^2.. }
+                },
+            };
+
+            return testDataEntries;
+        }
+
+        public class SplitTestData
+        {
+            public ustring Source;
+            public object SearchTerm;
+            public Range[] ExpectedRanges;
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Manipulation.cs
new file mode 100644 (file)
index 0000000..cc42dbb
--- /dev/null
@@ -0,0 +1,329 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Linq;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public partial class Utf8SpanTests
+    {
+        private delegate Utf8Span.SplitResult Utf8SpanSplitDelegate(Utf8Span span, Utf8StringSplitOptions splitOptions);
+
+        [Fact]
+        public static void Split_EmptySearchSpan_Throws()
+        {
+            // Shouldn't be able to split on an empty UTF-8 span.
+            // Such an enumerator would iterate forever, so we forbid it.
+
+            var ex = Assert.Throws<ArgumentException>(() => { u8("Hello").AsSpan().Split(Utf8Span.Empty); });
+            Assert.Equal("separator", ex.ParamName);
+        }
+
+        [Fact]
+        public static void Split_InvalidChar_Throws()
+        {
+            // Shouldn't be able to split on a standalone surrogate char
+            // Other search methods (TryFind) return false when given a standalone surrogate char as input,
+            // but the Split methods returns a complex data structure instead of a simple bool. So to keep
+            // the logic of that data structure relatively simple we'll forbid the bad char at the call site.
+
+            var ex = Assert.Throws<ArgumentOutOfRangeException>(() => { u8("Hello").AsSpan().Split('\ud800'); });
+            Assert.Equal("separator", ex.ParamName);
+        }
+
+        [Fact]
+        public static void Split_Char_NullInput()
+        {
+            // First, make sure that <null>.Split(',') yields a single-element [ <null> ].
+
+            Utf8Span source = Utf8Span.Empty;
+
+            var enumerator = source.Split(',').GetEnumerator();
+            Assert.True(enumerator.MoveNext());
+            Assert.True(source.Bytes == enumerator.Current.Bytes); // referential equality
+            Assert.False(enumerator.MoveNext());
+
+            // Next, make sure that if "remove empty entries" is specified, yields the empty set [ ].
+
+            enumerator = source.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries).GetEnumerator();
+            Assert.False(enumerator.MoveNext());
+        }
+
+        [Theory]
+        [MemberData(nameof(SplitData_CharSeparator))]
+        public static void Split_Char(ustring source, char separator, Range[] expectedRanges)
+        {
+            SplitTest_Common(source, (span, splitOptions) => span.Split(separator, splitOptions), expectedRanges);
+        }
+
+        [Fact]
+        public static void Split_Deconstruct()
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("a,b,c,d,e");
+            Utf8Span span = boundedSpan.Span;
+
+            // Note referential equality checks below (since we want to know exact slices
+            // into the original buffer), not deep (textual) equality checks.
+
+            {
+                (Utf8Span a, Utf8Span b) = span.Split('x'); // not found
+                Assert.True(a.Bytes == span.Bytes, "Expected referential equality of input.");
+                Assert.True(b.Bytes == default);
+            }
+
+            {
+                (Utf8Span a, Utf8Span b) = span.Split(',');
+                Assert.True(a.Bytes == span.Bytes[..1]); // "a"
+                Assert.True(b.Bytes == span.Bytes[2..]); // "b,c,d,e"
+            }
+
+            {
+                (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e) = span.Split(',');
+                Assert.True(a.Bytes == span.Bytes[0..1]); // "a"
+                Assert.True(b.Bytes == span.Bytes[2..3]); // "b"
+                Assert.True(c.Bytes == span.Bytes[4..5]); // "c"
+                Assert.True(d.Bytes == span.Bytes[6..7]); // "d"
+                Assert.True(e.Bytes == span.Bytes[8..9]); // "e"
+            }
+
+            {
+                (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e, Utf8Span f, Utf8Span g, Utf8Span h) = span.Split(',');
+                Assert.True(a.Bytes == span.Bytes[0..1]); // "a"
+                Assert.True(b.Bytes == span.Bytes[2..3]); // "b"
+                Assert.True(c.Bytes == span.Bytes[4..5]); // "c"
+                Assert.True(d.Bytes == span.Bytes[6..7]); // "d"
+                Assert.True(e.Bytes == span.Bytes[8..9]); // "e"
+                Assert.True(f.Bytes == default);
+                Assert.True(g.Bytes == default);
+                Assert.True(h.Bytes == default);
+            }
+        }
+
+        [Fact]
+        public static void Split_Deconstruct_WithOptions()
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("a, , b, c,, d, e");
+            Utf8Span span = boundedSpan.Span;
+
+            // Note referential equality checks below (since we want to know exact slices
+            // into the original buffer), not deep (textual) equality checks.
+
+            {
+                (Utf8Span a, Utf8Span b) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries);
+                Assert.True(a.Bytes == span.Bytes[..1]); // "a"
+                Assert.True(b.Bytes == span.Bytes[2..]); // " , b, c,, d, e"
+            }
+
+            {
+                (Utf8Span a, Utf8Span x, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries);
+                Assert.True(a.Bytes == span.Bytes[0..1]); // "a"
+                Assert.True(x.Bytes == span.Bytes[2..3]); // " "
+                Assert.True(b.Bytes == span.Bytes[4..6]); // " b"
+                Assert.True(c.Bytes == span.Bytes[7..9]); // " c"
+                Assert.True(d.Bytes == span.Bytes[11..13]); // " d"
+                Assert.True(e.Bytes == span.Bytes[14..]); // " e"
+            }
+
+            {
+                (Utf8Span a, Utf8Span b, Utf8Span c, Utf8Span d, Utf8Span e, Utf8Span f, Utf8Span g, Utf8Span h) = span.Split(',', Utf8StringSplitOptions.RemoveEmptyEntries | Utf8StringSplitOptions.TrimEntries);
+                Assert.True(a.Bytes == span.Bytes[0..1]); // "a"
+                Assert.True(b.Bytes == span.Bytes[5..6]); // "b"
+                Assert.True(c.Bytes == span.Bytes[8..9]); // "c"
+                Assert.True(d.Bytes == span.Bytes[12..13]); // "d"
+                Assert.True(e.Bytes == span.Bytes[15..]); // "e"
+                Assert.True(f.Bytes == default);
+                Assert.True(g.Bytes == default);
+                Assert.True(h.Bytes == default);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SplitData_RuneSeparator))]
+        public static void Split_Rune(ustring source, Rune separator, Range[] expectedRanges)
+        {
+            SplitTest_Common(source, (span, splitOptions) => span.Split(separator, splitOptions), expectedRanges);
+        }
+
+        [Theory]
+        [MemberData(nameof(SplitData_Utf8SpanSeparator))]
+        public static void Split_Utf8Span(ustring source, ustring separator, Range[] expectedRanges)
+        {
+            SplitTest_Common(source, (span, splitOptions) => span.Split(separator.AsSpan(), splitOptions), expectedRanges);
+        }
+
+        private static void SplitTest_Common(ustring source, Utf8SpanSplitDelegate splitAction, Range[] expectedRanges)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+            Utf8Span span = boundedSpan.Span;
+            int totalSpanLengthInBytes = span.Bytes.Length;
+            source = null; // to avoid inadvertently using this for the remainder of the method
+
+            // First, run the split with default options and make sure the ranges are equivalent
+
+            List<Range> actualRanges = new List<Range>();
+            foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.None))
+            {
+                actualRanges.Add(GetRangeOfSubspan(span, slice));
+            }
+
+            Assert.Equal(expectedRanges, actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes));
+
+            // Next, run the split with empty entries removed
+
+            actualRanges = new List<Range>();
+            foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.RemoveEmptyEntries))
+            {
+                actualRanges.Add(GetRangeOfSubspan(span, slice));
+            }
+
+            Assert.Equal(expectedRanges.Where(range => !range.IsEmpty(totalSpanLengthInBytes)), actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes));
+
+            // Next, run the split with results trimmed (but allowing empty results)
+
+            expectedRanges = (Range[])expectedRanges.Clone(); // clone the array since we're about to mutate it
+            for (int i = 0; i < expectedRanges.Length; i++)
+            {
+                expectedRanges[i] = GetRangeOfSubspan(span, span[expectedRanges[i]].Trim());
+            }
+
+            actualRanges = new List<Range>();
+            foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries))
+            {
+                actualRanges.Add(GetRangeOfSubspan(span, slice));
+            }
+
+            Assert.Equal(expectedRanges, actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes));
+
+            // Finally, run the split both trimmed and with empty entries removed
+
+            actualRanges = new List<Range>();
+            foreach (Utf8Span slice in splitAction(span, Utf8StringSplitOptions.TrimEntries | Utf8StringSplitOptions.RemoveEmptyEntries))
+            {
+                actualRanges.Add(GetRangeOfSubspan(span, slice));
+            }
+
+            Assert.Equal(expectedRanges.Where(range => !range.IsEmpty(totalSpanLengthInBytes)), actualRanges, new RangeEqualityComparer(totalSpanLengthInBytes));
+        }
+
+        [Theory]
+        [MemberData(nameof(Trim_TestData))]
+        public static void Trim(string input)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act
+
+            Utf8Span trimmed = span.Trim();
+
+            // Assert
+            // Compute the trim manually and ensure it matches the trimmed span's characteristics.
+
+            ReadOnlySpan<byte> utf8Bytes = span.Bytes;
+            while (!utf8Bytes.IsEmpty)
+            {
+                OperationStatus status = Rune.DecodeFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed);
+                Assert.Equal(OperationStatus.Done, status);
+
+                if (!Rune.IsWhiteSpace(decodedRune))
+                {
+                    break;
+                }
+
+                utf8Bytes = utf8Bytes.Slice(bytesConsumed);
+            }
+            while (!utf8Bytes.IsEmpty)
+            {
+                OperationStatus status = Rune.DecodeLastFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed);
+                Assert.Equal(OperationStatus.Done, status);
+
+                if (!Rune.IsWhiteSpace(decodedRune))
+                {
+                    break;
+                }
+
+                utf8Bytes = utf8Bytes[..^bytesConsumed];
+            }
+
+            Assert.True(trimmed.Bytes == utf8Bytes); // must be an exact buffer match (address + length)
+        }
+
+        [Theory]
+        [MemberData(nameof(Trim_TestData))]
+        public static void TrimEnd(string input)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act
+
+            Utf8Span trimmed = span.TrimEnd();
+
+            // Assert
+            // Compute the trim manually and ensure it matches the trimmed span's characteristics.
+
+            ReadOnlySpan<byte> utf8Bytes = span.Bytes;
+            while (!utf8Bytes.IsEmpty)
+            {
+                OperationStatus status = Rune.DecodeLastFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed);
+                Assert.Equal(OperationStatus.Done, status);
+
+                if (!Rune.IsWhiteSpace(decodedRune))
+                {
+                    break;
+                }
+
+                utf8Bytes = utf8Bytes[..^bytesConsumed];
+            }
+
+            Assert.True(trimmed.Bytes == utf8Bytes); // must be an exact buffer match (address + length)
+        }
+
+        [Theory]
+        [MemberData(nameof(Trim_TestData))]
+        public static void TrimStart(string input)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act
+
+            Utf8Span trimmed = span.TrimStart();
+
+            // Assert
+            // Compute the trim manually and ensure it matches the trimmed span's characteristics.
+
+            ReadOnlySpan<byte> utf8Bytes = span.Bytes;
+            while (!utf8Bytes.IsEmpty)
+            {
+                OperationStatus status = Rune.DecodeFromUtf8(utf8Bytes, out Rune decodedRune, out int bytesConsumed);
+                Assert.Equal(OperationStatus.Done, status);
+
+                if (!Rune.IsWhiteSpace(decodedRune))
+                {
+                    break;
+                }
+
+                utf8Bytes = utf8Bytes.Slice(bytesConsumed);
+            }
+
+            Assert.True(trimmed.Bytes == utf8Bytes); // must be an exact buffer match (address + length)
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.TestData.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.TestData.cs
new file mode 100644 (file)
index 0000000..19ccfd4
--- /dev/null
@@ -0,0 +1,576 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Globalization;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public partial class Utf8SpanTests
+    {
+        public static IEnumerable<object[]> TryFindData_Char_Ordinal()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal) || entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                {
+                    continue;
+                }
+
+                if (!TryParseSearchTermAsChar(entry.SearchTerm, out char searchChar))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchChar,
+                    entry.ExpectedFirstMatch,
+                    entry.ExpectedLastMatch,
+                };
+            }
+        }
+
+        public static IEnumerable<object[]> TryFindData_Char_WithComparison()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!TryParseSearchTermAsChar(entry.SearchTerm, out char searchChar))
+                {
+                    continue;
+                }
+
+                if (entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal))
+                {
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.Ordinal,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.OrdinalIgnoreCase,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+
+                foreach (CultureInfo culture in entry.AdditionalCultures ?? Array.Empty<CultureInfo>())
+                {
+                    if (culture == CultureInfo.InvariantCulture)
+                    {
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                        {
+                            yield return new object[]
+                            {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.InvariantCulture,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                            };
+                        }
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                        {
+                            yield return new object[]
+                            {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.InvariantCultureIgnoreCase,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                            };
+                        }
+                    }
+
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.CurrentCulture,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchChar,
+                            StringComparison.CurrentCultureIgnoreCase,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+            }
+        }
+
+        public static IEnumerable<object[]> TryFindData_Rune_Ordinal()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal) || entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                {
+                    continue;
+                }
+
+                if (!TryParseSearchTermAsRune(entry.SearchTerm, out Rune searchRune))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchRune,
+                    entry.ExpectedFirstMatch,
+                    entry.ExpectedLastMatch,
+                };
+            }
+        }
+
+        public static IEnumerable<object[]> TryFindData_Rune_WithComparison()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!TryParseSearchTermAsRune(entry.SearchTerm, out Rune searchRune))
+                {
+                    continue;
+                }
+
+                if (entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal))
+                {
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchRune,
+                            StringComparison.Ordinal,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchRune,
+                            StringComparison.OrdinalIgnoreCase,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+
+                foreach (CultureInfo culture in entry.AdditionalCultures ?? Array.Empty<CultureInfo>())
+                {
+                    if (culture == CultureInfo.InvariantCulture)
+                    {
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                        {
+                            yield return new object[]
+                            {
+                                entry.Source,
+                                searchRune,
+                                StringComparison.InvariantCulture,
+                                null /* culture */,
+                                entry.ExpectedFirstMatch,
+                                entry.ExpectedLastMatch,
+                            };
+                        }
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                        {
+                            yield return new object[]
+                            {
+                                entry.Source,
+                                searchRune,
+                                StringComparison.InvariantCultureIgnoreCase,
+                                null /* culture */,
+                                entry.ExpectedFirstMatch,
+                                entry.ExpectedLastMatch,
+                            };
+                        }
+                    }
+
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchRune,
+                            StringComparison.CurrentCulture,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchRune,
+                            StringComparison.CurrentCultureIgnoreCase,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+            }
+        }
+
+        public static IEnumerable<object[]> TryFindData_Utf8Span_Ordinal()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal) || entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                {
+                    continue;
+                }
+
+                if (!TryParseSearchTermAsUtf8String(entry.SearchTerm, out ustring searchTerm))
+                {
+                    continue;
+                }
+
+                yield return new object[]
+                {
+                    entry.Source,
+                    searchTerm,
+                    entry.ExpectedFirstMatch,
+                    entry.ExpectedLastMatch,
+                };
+            }
+        }
+
+        public static IEnumerable<object[]> TryFindData_Utf8Span_WithComparison()
+        {
+            foreach (TryFindTestData entry in TryFindData_All())
+            {
+                if (!TryParseSearchTermAsUtf8String(entry.SearchTerm, out ustring searchTerm))
+                {
+                    continue;
+                }
+
+                if (entry.Options.HasFlag(TryFindTestDataOptions.TestOrdinal))
+                {
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchTerm,
+                            StringComparison.Ordinal,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchTerm,
+                            StringComparison.OrdinalIgnoreCase,
+                            null /* culture */,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+
+                foreach (CultureInfo culture in entry.AdditionalCultures ?? Array.Empty<CultureInfo>())
+                {
+                    if (culture == CultureInfo.InvariantCulture)
+                    {
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                        {
+                            yield return new object[]
+                            {
+                                entry.Source,
+                                searchTerm,
+                                StringComparison.InvariantCulture,
+                                null /* culture */,
+                                entry.ExpectedFirstMatch,
+                                entry.ExpectedLastMatch,
+                            };
+                        }
+                        if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                        {
+                            yield return new object[]
+                            {
+                                entry.Source,
+                                searchTerm,
+                                StringComparison.InvariantCultureIgnoreCase,
+                                null /* culture */,
+                                entry.ExpectedFirstMatch,
+                                entry.ExpectedLastMatch,
+                            };
+                        }
+                    }
+
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestIgnoreCaseOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchTerm,
+                            StringComparison.CurrentCulture,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                    if (!entry.Options.HasFlag(TryFindTestDataOptions.TestCaseSensitiveOnly))
+                    {
+                        yield return new object[]
+                        {
+                            entry.Source,
+                            searchTerm,
+                            StringComparison.CurrentCultureIgnoreCase,
+                            culture,
+                            entry.ExpectedFirstMatch,
+                            entry.ExpectedLastMatch,
+                        };
+                    }
+                }
+            }
+        }
+
+        private static IEnumerable<TryFindTestData> TryFindData_All()
+        {
+            CultureInfo inv = CultureInfo.InvariantCulture;
+            CultureInfo en_US = CultureInfo.GetCultureInfo("en-US");
+            CultureInfo tr_TR = CultureInfo.GetCultureInfo("tr-TR");
+            CultureInfo hu_HU = CultureInfo.GetCultureInfo("hu-HU");
+
+            TryFindTestData[] testDataEntries = new TryFindTestData[]
+            {
+                new TryFindTestData
+                {
+                    // Searching for the empty string within the empty string should result in 0..0 / ^0..^0 across all comparers and all cultures
+                    Source = null,
+                    SearchTerm = null,
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv, en_US, tr_TR, hu_HU },
+                    ExpectedFirstMatch = 0..0,
+                    ExpectedLastMatch = ^0..^0,
+                },
+                new TryFindTestData
+                {
+                    // Searching for the empty string within a non-empty string should result in 0..0 / ^0..^0 across all comparers and all cultures
+                    Source = u8("Hello"),
+                    SearchTerm = null,
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv, en_US, tr_TR, hu_HU },
+                    ExpectedFirstMatch = 0..0,
+                    ExpectedLastMatch = ^0..^0,
+                },
+                new TryFindTestData
+                {
+                    // Searching for a non-empty string within an empty string should fail across all comparers and all cultures
+                    Source = null,
+                    SearchTerm = u8("Hello"),
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv, en_US, tr_TR, hu_HU },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // Searching for the null terminator shouldn't match unless the input contains a null terminator
+                    Source = u8("Hello"),
+                    SearchTerm = '\0',
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = null,
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // Searching for the null terminator shouldn't match unless the input contains a null terminator
+                    Source = u8("H\0ell\0o"),
+                    SearchTerm = '\0',
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = null,
+                    ExpectedFirstMatch = 1..2,
+                    ExpectedLastMatch = ^2..^1,
+                },
+                new TryFindTestData
+                {
+                    // Simple ASCII search with success (case-sensitive)
+                    Source = u8("Hello"),
+                    SearchTerm = 'l',
+                    Options = TryFindTestDataOptions.TestOrdinal | TryFindTestDataOptions.TestCaseSensitiveOnly,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = 2..3,
+                    ExpectedLastMatch = 3..4,
+                },
+                new TryFindTestData
+                {
+                    // Simple ASCII search with failure (case-sensitive)
+                    Source = u8("Hello"),
+                    SearchTerm = 'L',
+                    Options = TryFindTestDataOptions.TestOrdinal | TryFindTestDataOptions.TestCaseSensitiveOnly,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // Simple ASCII search with success (case-insensitive)
+                    Source = u8("Hello"),
+                    SearchTerm = 'L',
+                    Options = TryFindTestDataOptions.TestOrdinal | TryFindTestDataOptions.TestIgnoreCaseOnly,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = 2..3,
+                    ExpectedLastMatch = 3..4,
+                },
+                new TryFindTestData
+                {
+                    // U+1F600 GRINNING FACE, should match an exact Rune search
+                    Source = u8("x\U0001F600y"),
+                    SearchTerm = new Rune(0x1F600),
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = 1..5,
+                    ExpectedLastMatch = 1..5,
+                },
+                new TryFindTestData
+                {
+                    // U+1F600 GRINNING FACE, shouldn't match looking for individual UTF-16 surrogate chars
+                    Source = u8("x\ud83d\ude00y"),
+                    SearchTerm = '\ud83d',
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // U+1F600 GRINNING FACE, shouldn't match on the standalone [ F0 ] byte that begins the multi-byte sequence
+                    Source = u8("x\ud83d\ude00y"),
+                    SearchTerm = '\u00f0',
+                    Options = TryFindTestDataOptions.TestOrdinal,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // hu_HU shouldn't match "d" within "dz"
+                    Source = u8("ab_dz_ba"),
+                    SearchTerm = 'd',
+                    Options = TryFindTestDataOptions.None,
+                    AdditionalCultures = new CultureInfo[] { hu_HU },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // Turkish I, case-sensitive
+                    Source = u8("\u0069\u0130\u0131\u0049"), // iİıI
+                    SearchTerm = 'i',
+                    Options = TryFindTestDataOptions.TestCaseSensitiveOnly,
+                    AdditionalCultures = new CultureInfo[] { tr_TR },
+                    ExpectedFirstMatch = 0..1,
+                    ExpectedLastMatch = 0..1,
+                },
+                new TryFindTestData
+                {
+                    // Turkish I, case-insensitive
+                    Source = u8("\u0069\u0130\u0131\u0049"), // iİıI
+                    SearchTerm = 'i',
+                    Options = TryFindTestDataOptions.TestIgnoreCaseOnly,
+                    AdditionalCultures = new CultureInfo[] { tr_TR },
+                    ExpectedFirstMatch = 0..1,
+                    ExpectedLastMatch = 1..3,
+                },
+                new TryFindTestData
+                {
+                    // denormalized forms, no match
+                    Source = u8("a\u0308e\u0308A\u0308E\u0308"), // äëÄË (denormalized)
+                    SearchTerm = 'e', // shouldn't match letter paired with diacritic
+                    Options = TryFindTestDataOptions.None,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = null,
+                    ExpectedLastMatch = null,
+                },
+                new TryFindTestData
+                {
+                    // denormalized forms, case-sensitive
+                    Source = u8("a\u0308e\u0308A\u0308E\u0308"), // äëÄË (denormalized)
+                    SearchTerm = '\u00eb', // ë, normalized form
+                    Options = TryFindTestDataOptions.TestCaseSensitiveOnly,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = 3..6,
+                    ExpectedLastMatch = 3..6,
+                },
+                new TryFindTestData
+                {
+                    // denormalized forms, case-insensitive
+                    Source = u8("a\u0308e\u0308A\u0308E\u0308"), // äëÄË (denormalized)
+                    SearchTerm = '\u00eb', // ë, normalized form
+                    Options = TryFindTestDataOptions.TestIgnoreCaseOnly,
+                    AdditionalCultures = new CultureInfo[] { inv },
+                    ExpectedFirstMatch = 3..6,
+                    ExpectedLastMatch = ^3..,
+                },
+            };
+
+            return testDataEntries;
+        }
+
+        public class TryFindTestData
+        {
+            public ustring Source;
+            public object SearchTerm;
+            public TryFindTestDataOptions Options;
+            public CultureInfo[] AdditionalCultures;
+            public Range? ExpectedFirstMatch;
+            public Range? ExpectedLastMatch;
+        }
+
+        [Flags]
+        public enum TryFindTestDataOptions
+        {
+            None = 0,
+            TestOrdinal = 1 << 0,
+            TestCaseSensitiveOnly = 1 << 1,
+            TestIgnoreCaseOnly = 2 << 1,
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.Searching.cs
new file mode 100644 (file)
index 0000000..7df9259
--- /dev/null
@@ -0,0 +1,416 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Globalization;
+using System.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public unsafe partial class Utf8SpanTests
+    {
+        [Theory]
+        [MemberData(nameof(TryFindData_Char_Ordinal))]
+        public static void TryFind_Char_Ordinal(ustring source, char searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+            Utf8Span searchSpan = boundedSpan.Span;
+            source = null; // to avoid accidentally using this for the remainder of the test
+
+            // First, search forward
+
+            bool wasFound = searchSpan.TryFind(searchTerm, out Range actualForwardMatch);
+            Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+            }
+
+            // Also check Contains / StartsWith / SplitOn
+
+            Assert.Equal(wasFound, searchSpan.Contains(searchTerm));
+            Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm));
+
+            (var before, var after) = searchSpan.SplitOn(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+
+            // Now search backward
+
+            wasFound = searchSpan.TryFindLast(searchTerm, out Range actualBackwardMatch);
+            Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+            }
+
+            // Also check EndsWith / SplitOnLast
+
+            Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm));
+
+            (before, after) = searchSpan.SplitOnLast(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+        }
+
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [MemberData(nameof(TryFindData_Char_WithComparison))]
+        public static void TryFind_Char_WithComparison(ustring source, char searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            RunOnDedicatedThread(() =>
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+                Utf8Span searchSpan = boundedSpan.Span;
+                source = null; // to avoid accidentally using this for the remainder of the test
+
+                if (currentCulture != null)
+                {
+                    CultureInfo.CurrentCulture = currentCulture;
+                }
+
+                // First, search forward
+
+                bool wasFound = searchSpan.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+                Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+                }
+
+                // Also check Contains / StartsWith / SplitOn
+
+                Assert.Equal(wasFound, searchSpan.Contains(searchTerm, comparison));
+                Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm, comparison));
+
+                (var before, var after) = searchSpan.SplitOn(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+
+                // Now search backward
+
+                wasFound = searchSpan.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+                Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+                }
+
+                // Also check EndsWith / SplitOnLast
+
+                Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm, comparison));
+
+                (before, after) = searchSpan.SplitOnLast(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+            });
+        }
+
+        [Theory]
+        [MemberData(nameof(TryFindData_Rune_Ordinal))]
+        public static void TryFind_Rune_Ordinal(ustring source, Rune searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+            Utf8Span searchSpan = boundedSpan.Span;
+            source = null; // to avoid accidentally using this for the remainder of the test
+
+            // First, search forward
+
+            bool wasFound = searchSpan.TryFind(searchTerm, out Range actualForwardMatch);
+            Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+            }
+
+            // Also check Contains / StartsWith / SplitOn
+
+            Assert.Equal(wasFound, searchSpan.Contains(searchTerm));
+            Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm));
+
+            (var before, var after) = searchSpan.SplitOn(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+
+            // Now search backward
+
+            wasFound = searchSpan.TryFindLast(searchTerm, out Range actualBackwardMatch);
+            Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+            }
+
+            // Also check EndsWith / SplitOnLast
+
+            Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm));
+
+            (before, after) = searchSpan.SplitOnLast(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+        }
+
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [MemberData(nameof(TryFindData_Rune_WithComparison))]
+        public static void TryFind_Rune_WithComparison(ustring source, Rune searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            RunOnDedicatedThread(() =>
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+                Utf8Span searchSpan = boundedSpan.Span;
+                source = null; // to avoid accidentally using this for the remainder of the test
+
+                if (currentCulture != null)
+                {
+                    CultureInfo.CurrentCulture = currentCulture;
+                }
+
+                // First, search forward
+
+                bool wasFound = searchSpan.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+                Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+                }
+
+                // Also check Contains / StartsWith / SplitOn
+
+                Assert.Equal(wasFound, searchSpan.Contains(searchTerm, comparison));
+                Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm, comparison));
+
+                (var before, var after) = searchSpan.SplitOn(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+
+                // Now search backward
+
+                wasFound = searchSpan.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+                Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+                }
+
+                // Also check EndsWith / SplitOnLast
+
+                Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm, comparison));
+
+                (before, after) = searchSpan.SplitOnLast(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+            });
+        }
+
+        [Theory]
+        [MemberData(nameof(TryFindData_Utf8Span_Ordinal))]
+        public static void TryFind_Utf8Span_Ordinal(ustring source, ustring searchTerm, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+            Utf8Span searchSpan = boundedSpan.Span;
+            source = null; // to avoid accidentally using this for the remainder of the test
+
+            // First, search forward
+
+            bool wasFound = searchSpan.TryFind(searchTerm, out Range actualForwardMatch);
+            Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+            }
+
+            // Also check Contains / StartsWith / SplitOn
+
+            Assert.Equal(wasFound, searchSpan.Contains(searchTerm));
+            Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm));
+
+            (var before, var after) = searchSpan.SplitOn(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+
+            // Now search backward
+
+            wasFound = searchSpan.TryFindLast(searchTerm, out Range actualBackwardMatch);
+            Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+            if (wasFound)
+            {
+                AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+            }
+
+            // Also check EndsWith / SplitOnLast
+
+            Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm));
+
+            (before, after) = searchSpan.SplitOnLast(searchTerm);
+            if (wasFound)
+            {
+                Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+            }
+            else
+            {
+                Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                Assert.True(after.IsNull());
+            }
+        }
+
+        [Theory]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        [MemberData(nameof(TryFindData_Utf8Span_WithComparison))]
+        public static void TryFind_Utf8Span_WithComparison(ustring source, ustring searchTerm, StringComparison comparison, CultureInfo currentCulture, Range? expectedForwardMatch, Range? expectedBackwardMatch)
+        {
+            RunOnDedicatedThread(() =>
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(source.AsBytes());
+                Utf8Span searchSpan = boundedSpan.Span;
+                source = null; // to avoid accidentally using this for the remainder of the test
+
+                if (currentCulture != null)
+                {
+                    CultureInfo.CurrentCulture = currentCulture;
+                }
+
+                // First, search forward
+
+                bool wasFound = searchSpan.TryFind(searchTerm, comparison, out Range actualForwardMatch);
+                Assert.Equal(expectedForwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedForwardMatch.Value, actualForwardMatch);
+                }
+
+                // Also check Contains / StartsWith / SplitOn
+
+                Assert.Equal(wasFound, searchSpan.Contains(searchTerm, comparison));
+                Assert.Equal(wasFound && searchSpan.Bytes[..actualForwardMatch.Start].IsEmpty, searchSpan.StartsWith(searchTerm, comparison));
+
+                (var before, var after) = searchSpan.SplitOn(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualForwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualForwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+
+                // Now search backward
+
+                wasFound = searchSpan.TryFindLast(searchTerm, comparison, out Range actualBackwardMatch);
+                Assert.Equal(expectedBackwardMatch.HasValue, wasFound);
+
+                if (wasFound)
+                {
+                    AssertRangesEqual(searchSpan.Bytes.Length, expectedBackwardMatch.Value, actualBackwardMatch);
+                }
+
+                // Also check EndsWith / SplitOnLast
+
+                Assert.Equal(wasFound && searchSpan.Bytes[actualBackwardMatch.End..].IsEmpty, searchSpan.EndsWith(searchTerm, comparison));
+
+                (before, after) = searchSpan.SplitOnLast(searchTerm, comparison);
+                if (wasFound)
+                {
+                    Assert.True(searchSpan.Bytes[..actualBackwardMatch.Start] == before.Bytes); // check for referential equality
+                    Assert.True(searchSpan.Bytes[actualBackwardMatch.End..] == after.Bytes); // check for referential equality
+                }
+                else
+                {
+                    Assert.True(searchSpan.Bytes == before.Bytes); // check for reference equality
+                    Assert.True(after.IsNull());
+                }
+            });
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.TestData.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.TestData.cs
new file mode 100644 (file)
index 0000000..290eb2d
--- /dev/null
@@ -0,0 +1,178 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Linq;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public unsafe partial class Utf8SpanTests
+    {
+        /// <summary>
+        /// All <see cref="Rune"/>s, U+0000..U+D800 and U+E000..U+10FFFF.
+        /// </summary>
+        private static IEnumerable<Rune> AllRunes
+        {
+            get
+            {
+                for (uint i = 0; i < 0xD800; i++)
+                {
+                    yield return new Rune(i);
+                }
+                for (uint i = 0xE000; i <= 0x10FFFF; i++)
+                {
+                    yield return new Rune(i);
+                }
+            }
+        }
+
+        /// <summary>
+        /// All <see cref="Rune"/>s where <see cref="Rune.IsWhiteSpace(Rune)"/> returns <see langword="true"/>.
+        /// </summary>
+        private static readonly Lazy<Rune[]> WhiteSpaceRunes = new Lazy<Rune[]>(() => AllRunes.Where(Rune.IsWhiteSpace).ToArray());
+
+        public static IEnumerable<object[]> Trim_TestData()
+        {
+            string[] testData = new string[]
+            {
+                null, // null
+                "", // empty
+                "\0", // contains null character - shouldn't be trimmed
+                "Hello", // simple non-whitespace ASCII data
+                "\u0009Hello\u000d", // C0 whitespace characters
+                "\u0009\u0008\u0009Hello\u000e\u000b", // C0 whitespace + non-whitespace characters
+                " Hello! ", // simple space chars (plus !, since it's adjacent to U+0020 SPACE)
+                "\u0085\u0084\u0086\u0085", // U+0085 NEXT LINE (NEL), surrounded by adjacent non-whitespace chars
+            };
+
+            foreach (string entry in testData)
+            {
+                yield return new object[] { entry };
+            }
+
+            // A string with every possible whitespace character, just to test the limits
+
+            StringBuilder builder = new StringBuilder();
+            foreach (Rune whitespaceRune in WhiteSpaceRunes.Value)
+            {
+                builder.Append(whitespaceRune);
+            }
+            builder.Append("xyz");
+            foreach (Rune whitespaceRune in WhiteSpaceRunes.Value)
+            {
+                builder.Append(whitespaceRune);
+            }
+
+            yield return new object[] { builder.ToString() };
+        }
+
+        private static bool TryParseSearchTermAsChar(object searchTerm, out char parsed)
+        {
+            if (searchTerm is char ch)
+            {
+                parsed = ch;
+                return true;
+            }
+            else if (searchTerm is Rune r)
+            {
+                if (r.IsBmp)
+                {
+                    parsed = (char)r.Value;
+                    return true;
+                }
+            }
+            else if (searchTerm is string str)
+            {
+                if (str.Length == 1)
+                {
+                    parsed = str[0];
+                    return true;
+                }
+            }
+            else if (searchTerm is ustring ustr)
+            {
+                var asString = ustr.ToString();
+                if (asString.Length == 1)
+                {
+                    parsed = asString[0];
+                    return true;
+                }
+            }
+
+            parsed = default; // failed to turn the search term into a single char
+            return false;
+        }
+
+        private static bool TryParseSearchTermAsRune(object searchTerm, out Rune parsed)
+        {
+            if (searchTerm is char ch)
+            {
+                return Rune.TryCreate(ch, out parsed);
+            }
+            else if (searchTerm is Rune r)
+            {
+                parsed = r;
+                return true;
+            }
+            else if (searchTerm is string str)
+            {
+                if (Rune.DecodeFromUtf16(str, out parsed, out int charsConsumed) == OperationStatus.Done
+                    && charsConsumed == str.Length)
+                {
+                    return true;
+                }
+            }
+            else if (searchTerm is ustring ustr)
+            {
+                if (Rune.DecodeFromUtf8(ustr.AsBytes(), out parsed, out int bytesConsumed) == OperationStatus.Done
+                    && bytesConsumed == ustr.GetByteLength())
+                {
+                    return true;
+                }
+            }
+
+            parsed = default; // failed to turn the search term into a single Rune
+            return false;
+        }
+
+        private static bool TryParseSearchTermAsUtf8String(object searchTerm, out ustring parsed)
+        {
+            if (searchTerm is char ch)
+            {
+                if (Rune.TryCreate(ch, out Rune rune))
+                {
+                    parsed = rune.ToUtf8String();
+                    return true;
+                }
+            }
+            else if (searchTerm is Rune r)
+            {
+                parsed = r.ToUtf8String();
+                return true;
+            }
+            else if (searchTerm is string str)
+            {
+                ustring asUtf8 = new ustring(str);
+                if (asUtf8.ToString() == str) // make sure this round-trips properly
+                {
+                    parsed = asUtf8;
+                    return true;
+                }
+            }
+            else if (searchTerm is ustring ustr)
+            {
+                parsed = ustr;
+                return true;
+            }
+
+            parsed = default; // failed to turn the search term into a ustring
+            return false;
+        }
+    }
+}
diff --git a/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.cs b/src/libraries/System.Utf8String.Experimental/tests/System/Utf8SpanTests.cs
new file mode 100644 (file)
index 0000000..25579c3
--- /dev/null
@@ -0,0 +1,353 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Tests;
+using Xunit;
+
+using static System.Tests.Utf8TestUtilities;
+
+using ustring = System.Utf8String;
+
+namespace System.Text.Tests
+{
+    public unsafe partial class Utf8SpanTests
+    {
+        [Fact]
+        public static void BytesProperty_FromCustomBytes()
+        {
+            byte[] bytes = Encoding.UTF8.GetBytes("Hello!");
+            Assert.True(bytes.AsSpan() == Utf8Span.UnsafeCreateWithoutValidation(bytes).Bytes);
+        }
+
+        [Fact]
+        public static void BytesProperty_FromEmpty()
+        {
+            Assert.True(Utf8Span.Empty.Bytes == ReadOnlySpan<byte>.Empty);
+        }
+
+        [Fact]
+        public static void BytesProperty_FromUtf8String()
+        {
+            ustring ustr = u8("Hello!");
+            Utf8Span uspan = new Utf8Span(ustr);
+
+            Assert.True(ustr.AsBytes() == uspan.Bytes);
+        }
+
+        [Fact]
+        public static void EmptyProperty()
+        {
+            // Act
+
+            Utf8Span span = Utf8Span.Empty;
+
+            // Assert
+            // GetPinnableReference should be 'null' to match behavior of empty ROS<byte>.GetPinnableReference();
+
+            Assert.True(span.IsEmpty);
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref Unsafe.AsRef(in span.GetPinnableReference())));
+            Assert.Equal(IntPtr.Zero, (IntPtr)(void*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(span.Bytes)));
+            Assert.Equal(0, span.Bytes.Length);
+        }
+
+        [Fact]
+        public static void GetHashCode_Ordinal()
+        {
+            // Generate 17 all-null strings and make sure they have unique hash codes.
+            // Assuming Marvin32 is a good PRF and has a full 32-bit output domain, we should
+            // expect this test to fail only once every ~30 million runs due to the birthday paradox.
+            // That should be good enough for inclusion as a unit test.
+
+            HashSet<int> seenHashCodes = new HashSet<int>();
+
+            for (int i = 0; i <= 16; i++)
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(new byte[i]);
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.True(seenHashCodes.Add(span.GetHashCode()), "This hash code was previously seen.");
+            }
+        }
+
+        [Fact]
+        [PlatformSpecific(TestPlatforms.Windows)]
+        public static void GetHashCode_WithComparison()
+        {
+            // Since hash code generation is randomized, it's possible (though unlikely) that
+            // we might see unanticipated collisions. It's ok if this unit test fails once in
+            // every few million runs, but if the unit test becomes truly flaky then that would
+            // be indicative of a larger problem with hash code generation.
+            //
+            // These tests also make sure that the hash code is computed over the buffer rather
+            // than over the reference.
+
+            // Ordinal
+
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("ababaaAA");
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.Equal(span[0..2].GetHashCode(StringComparison.Ordinal), span[2..4].GetHashCode(StringComparison.Ordinal));
+                Assert.NotEqual(span[4..6].GetHashCode(StringComparison.Ordinal), span[6..8].GetHashCode(StringComparison.Ordinal));
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.Ordinal), span[^0..].GetHashCode(StringComparison.Ordinal)); // null should equal empty
+            }
+
+            // OrdinalIgnoreCase
+
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("ababaaAA");
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.Equal(span[0..2].GetHashCode(StringComparison.OrdinalIgnoreCase), span[2..4].GetHashCode(StringComparison.OrdinalIgnoreCase));
+                Assert.Equal(span[4..6].GetHashCode(StringComparison.OrdinalIgnoreCase), span[6..8].GetHashCode(StringComparison.OrdinalIgnoreCase));
+                Assert.NotEqual(span[0..2].GetHashCode(StringComparison.OrdinalIgnoreCase), span[6..8].GetHashCode(StringComparison.OrdinalIgnoreCase));
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.OrdinalIgnoreCase), span[^0..].GetHashCode(StringComparison.OrdinalIgnoreCase)); // null should equal empty
+            }
+
+            // InvariantCulture
+
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("ae\u00e6AE\u00c6"); // U+00E6 = 'æ' LATIN SMALL LETTER AE, U+00E6 = 'Æ' LATIN CAPITAL LETTER AE
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.Equal(span[0..2].GetHashCode(StringComparison.InvariantCulture), span[2..4].GetHashCode(StringComparison.InvariantCulture));
+                Assert.NotEqual(span[0..2].GetHashCode(StringComparison.InvariantCulture), span[4..6].GetHashCode(StringComparison.InvariantCulture));
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.InvariantCulture), span[^0..].GetHashCode(StringComparison.InvariantCulture)); // null should equal empty
+            }
+
+            // InvariantCultureIgnoreCase
+
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("ae\u00e6AE\u00c6EA");
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.Equal(span[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), span[2..4].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+                Assert.Equal(span[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), span[6..8].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+                Assert.NotEqual(span[0..2].GetHashCode(StringComparison.InvariantCultureIgnoreCase), span[8..10].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.InvariantCultureIgnoreCase), span[^0..].GetHashCode(StringComparison.InvariantCultureIgnoreCase)); // null should equal empty
+            }
+
+            // Invariant culture should not match Turkish I case conversion
+
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("i\u0130"); // U+0130 = 'İ' LATIN CAPITAL LETTER I WITH DOT ABOVE
+                Utf8Span span = boundedSpan.Span;
+
+                Assert.NotEqual(span[0..1].GetHashCode(StringComparison.InvariantCultureIgnoreCase), span[1..3].GetHashCode(StringComparison.InvariantCultureIgnoreCase));
+            }
+
+            // CurrentCulture (we'll use tr-TR)
+
+            RunOnDedicatedThread(() =>
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("i\u0131\u0130Ii\u0131\u0130I"); // U+0131 = 'ı' LATIN SMALL LETTER DOTLESS I
+                Utf8Span span = boundedSpan.Span;
+
+                CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
+
+                Assert.Equal(span[0..6].GetHashCode(StringComparison.CurrentCulture), span[6..12].GetHashCode(StringComparison.CurrentCulture));
+                Assert.NotEqual(span[0..1].GetHashCode(StringComparison.CurrentCulture), span[1..3].GetHashCode(StringComparison.CurrentCulture));
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.CurrentCulture), span[^0..].GetHashCode(StringComparison.CurrentCulture)); // null should equal empty
+            });
+
+            // CurrentCultureIgnoreCase (we'll use tr-TR)
+
+            RunOnDedicatedThread(() =>
+            {
+                using BoundedUtf8Span boundedSpan = new BoundedUtf8Span("i\u0131\u0130Ii\u0131\u0130I");
+                Utf8Span span = boundedSpan.Span;
+
+                CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");
+
+                Assert.Equal(span[0..6].GetHashCode(StringComparison.CurrentCultureIgnoreCase), span[6..12].GetHashCode(StringComparison.CurrentCultureIgnoreCase));
+                Assert.NotEqual(span[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), span[1..3].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' shouldn't match 'ı'
+                Assert.Equal(span[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), span[3..5].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' should match 'İ'
+                Assert.NotEqual(span[0..1].GetHashCode(StringComparison.CurrentCultureIgnoreCase), span[5..6].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // 'i' shouldn't match 'I'
+                Assert.Equal(Utf8Span.Empty.GetHashCode(StringComparison.CurrentCultureIgnoreCase), span[^0..].GetHashCode(StringComparison.CurrentCultureIgnoreCase)); // null should equal empty
+            });
+        }
+
+        [Theory]
+        [InlineData("", true)]
+        [InlineData("Hello", true)]
+        [InlineData("\u1234", false)]
+        public static void IsAscii(string input, bool expected)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+
+            Assert.Equal(expected, boundedSpan.Span.IsAscii());
+        }
+
+        [Theory]
+        [InlineData(null, true)]
+        [InlineData("", true)]
+        [InlineData(" \u2028\u2029\t\v", true)]
+        [InlineData(" x\r\n", false)]
+        [InlineData("\r\nhello\r\n", false)]
+        [InlineData("\r\n\0\r\n", false)]
+        [InlineData("\r\n\r\n", true)]
+        public static void IsEmptyOrWhiteSpace(string input, bool expected)
+        {
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+
+            Assert.Equal(expected, boundedSpan.Span.IsEmptyOrWhiteSpace());
+        }
+
+        [Theory]
+        [InlineData("", "..")]
+        [InlineData("Hello", "1..")]
+        [InlineData("Hello", "1..2")]
+        [InlineData("Hello", "1..^2")]
+        [InlineData("Hello", "^2..")]
+        [InlineData("Hello", "^0..")]
+        [InlineData("résumé", "1..^2")] // include first 'é', exclude last 'é'
+        [InlineData("résumé", "^2..")] // include only last 'é'
+        public static void Indexer_Success(string input, string rangeExpression)
+        {
+            Range range = ParseRangeExpr(rangeExpression);
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+            Utf8Span originalSpan = boundedSpan.Span;
+            Utf8Span slicedSpan = originalSpan[range]; // shouldn't throw
+
+            ref byte startOfOriginalSpan = ref MemoryMarshal.GetReference(originalSpan.Bytes);
+            ref byte startOfSlicedSpan = ref MemoryMarshal.GetReference(slicedSpan.Bytes);
+
+            // Now ensure the slice was correctly produced by comparing the references directly.
+
+            (int offset, int length) = range.GetOffsetAndLength(originalSpan.Bytes.Length);
+            Assert.True(Unsafe.AreSame(ref startOfSlicedSpan, ref Unsafe.Add(ref startOfOriginalSpan, offset)));
+            Assert.Equal(length, slicedSpan.Bytes.Length);
+        }
+
+        [Theory]
+        [InlineData("résumé", "2..")] // try to split the first 'é'
+        [InlineData("résumé", "..^1")] // try to split the last 'é'
+        public static void Indexer_ThrowsIfTryToSplitMultiByteSubsequence(string input, string rangeExpression)
+        {
+            Range range = ParseRangeExpr(rangeExpression);
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(input);
+
+            Assert.Throws<InvalidOperationException>(() => { var _ = boundedSpan.Span[range]; });
+        }
+
+
+        [Theory]
+        [MemberData(nameof(TranscodingTestData))]
+        public static void ToCharArrayTest(string expected)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(expected);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act
+
+            char[] returned = span.ToCharArray();
+
+            // Assert
+
+            Assert.Equal(expected, returned); // IEnumerable<char>
+        }
+
+        [Fact]
+        public static void ToCharArrayTest_Null()
+        {
+            Assert.Same(Array.Empty<char>(), Utf8Span.Empty.ToCharArray());
+        }
+
+        [Theory]
+        [MemberData(nameof(TranscodingTestData))]
+        public static void ToCharsTest(string expected)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(expected);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act & assert, first with improperly-sized buffer
+
+            if (expected.Length > 0)
+            {
+                using BoundedMemory<char> boundedMemory = BoundedMemory.Allocate<char>(expected.Length - 1);
+                Assert.Equal(-1, span.ToChars(boundedMemory.Span));
+            }
+
+            // Then with properly-sized buffer and too-large buffer
+
+            for (int i = expected.Length; i <= expected.Length + 1; i++)
+            {
+                using BoundedMemory<char> boundedMemory = BoundedMemory.Allocate<char>(i);
+                Assert.Equal(expected.Length, span.ToChars(boundedMemory.Span));
+                Assert.True(boundedMemory.Span.Slice(0, expected.Length).SequenceEqual(expected));
+            }
+        }
+
+        [Fact]
+        public static void ToCharsTest_Null()
+        {
+            for (int i = 0; i <= 1; i++) // test both with properly-sized buffer and with too-large buffer
+            {
+                using BoundedMemory<char> boundedMemory = BoundedMemory.Allocate<char>(i);
+                Assert.Equal(0, Utf8Span.Empty.ToChars(boundedMemory.Span));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(TranscodingTestData))]
+        public static void ToStringTest(string expected)
+        {
+            // Arrange
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(expected);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act & assert
+
+            Assert.Equal(expected, span.ToString());
+        }
+
+        [Fact]
+        public static void ToStringTest_Null()
+        {
+            Assert.Same(string.Empty, Utf8Span.Empty.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(TranscodingTestData))]
+        public static void ToUtf8StringTest(string expected)
+        {
+            // Arrange
+
+            ustring utf8 = u8(expected);
+
+            using BoundedUtf8Span boundedSpan = new BoundedUtf8Span(expected);
+            Utf8Span span = boundedSpan.Span;
+
+            // Act & assert
+
+            Assert.Equal(utf8, span.ToUtf8String());
+        }
+
+        [Fact]
+        public static void ToUtf8StringTest_Null()
+        {
+            Assert.Same(ustring.Empty, Utf8Span.Empty.ToUtf8String());
+        }
+
+        public static IEnumerable<object[]> TranscodingTestData()
+        {
+            yield return new object[] { "" }; // empty
+            yield return new object[] { "Hello" }; // simple ASCII
+            yield return new object[] { "a\U00000123b\U00001234c\U00101234d" }; // with multi-byte sequences of varying lengths
+            yield return new object[] { "\uF8FF\uE000\U000FFFFF" }; // with scalars from the private use areas
+        }
+    }
+}
index 1ea63d1..96fe60f 100644 (file)
@@ -2,9 +2,14 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
+using System.Globalization;
 using System.IO;
 using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
 using System.Text;
+using System.Threading;
 using Xunit;
 
 namespace System.Tests
@@ -23,6 +28,110 @@ namespace System.Tests
             });
         }
 
+        public static int GetByteLength(this Utf8String value)
+        {
+            return value.AsBytes().Length;
+        }
+
+        public unsafe static bool IsNull(this Utf8Span span)
+        {
+            return Unsafe.AreSame(ref Unsafe.AsRef<byte>(null), ref MemoryMarshal.GetReference(span.Bytes));
+        }
+
+        /// <summary>
+        /// Parses an expression of the form "a..b" and returns a <see cref="Range"/>.
+        /// </summary>
+        public static Range ParseRangeExpr(ReadOnlySpan<char> expression)
+        {
+            int idxOfDots = expression.IndexOf("..", StringComparison.Ordinal);
+            if (idxOfDots < 0)
+            {
+                goto Error;
+            }
+
+            ReadOnlySpan<char> firstPart = expression[..idxOfDots].Trim();
+            Index firstIndex = Index.Start;
+
+            if (!firstPart.IsWhiteSpace())
+            {
+                bool fromEnd = false;
+
+                if (!firstPart.IsEmpty && firstPart[0] == '^')
+                {
+                    fromEnd = true;
+                    firstPart = firstPart[1..];
+                }
+
+                if (!int.TryParse(firstPart, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture, out int startIndex))
+                {
+                    goto Error;
+                }
+
+                firstIndex = new Index(startIndex, fromEnd);
+            }
+
+            ReadOnlySpan<char> secondPart = expression[(idxOfDots + 2)..].Trim();
+            Index secondIndex = Index.End;
+
+            if (!secondPart.IsWhiteSpace())
+            {
+                bool fromEnd = false;
+
+                if (!secondPart.IsEmpty && secondPart[0] == '^')
+                {
+                    fromEnd = true;
+                    secondPart = secondPart[1..];
+                }
+
+                if (!int.TryParse(secondPart, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite, CultureInfo.InvariantCulture, out int endIndex))
+                {
+                    goto Error;
+                }
+
+                secondIndex = new Index(endIndex, fromEnd);
+            }
+
+            return new Range(firstIndex, secondIndex);
+
+        Error:
+            throw new ArgumentException($"Range expression '{expression.ToString()}' is invalid.");
+        }
+
+        public static void AssertRangesEqual(int originalLength, Range expected, Range actual)
+        {
+            Assert.Equal(expected, actual, new RangeEqualityComparer(originalLength));
+        }
+
+        /// <summary>
+        /// Runs this test on its own dedicated thread; allows for setting CurrentCulture and other thread-statics.
+        /// </summary>
+        /// <param name="testCode"></param>
+        public static void RunOnDedicatedThread(Action testCode)
+        {
+            Assert.NotNull(testCode);
+
+            ExceptionDispatchInfo edi = default;
+            Thread newThread = new Thread(() =>
+            {
+                try
+                {
+                    testCode();
+                }
+                catch (Exception ex)
+                {
+                    edi = ExceptionDispatchInfo.Capture(ex);
+                }
+            });
+
+            newThread.Start();
+            newThread.Join();
+
+            if (edi != null)
+            {
+                edi.Throw();
+            }
+        }
+
         /// <summary>
         /// Mimics returning a literal <see cref="Utf8String"/> instance.
         /// </summary>
@@ -68,10 +177,36 @@ namespace System.Tests
             Utf8String newUtf8String = _utf8StringFactory.Value(buffer.Count);
             fixed (byte* pNewUtf8String = newUtf8String)
             {
-                buffer.AsSpan().CopyTo(new Span<byte>(pNewUtf8String, newUtf8String.Length));
+                buffer.AsSpan().CopyTo(new Span<byte>(pNewUtf8String, newUtf8String.GetByteLength()));
             }
 
             return newUtf8String;
         }
+
+        public unsafe static Range GetRangeOfSubspan<T>(ReadOnlySpan<T> outerSpan, ReadOnlySpan<T> innerSpan)
+        {
+            ulong byteOffset = (ulong)(void*)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(outerSpan), ref MemoryMarshal.GetReference(innerSpan));
+            ulong elementOffset = byteOffset / (uint)Unsafe.SizeOf<T>();
+
+            checked
+            {
+                int elementOffsetAsInt = (int)elementOffset;
+                Range retVal = elementOffsetAsInt..(elementOffsetAsInt + innerSpan.Length);
+
+                _ = outerSpan[retVal]; // call the real slice logic to make sure we're really within the outer span
+                return retVal;
+            }
+        }
+
+        public static Range GetRangeOfSubspan(Utf8Span outerSpan, Utf8Span innerSpan)
+        {
+            return GetRangeOfSubspan(outerSpan.Bytes, innerSpan.Bytes);
+        }
+
+        public static bool IsEmpty(this Range range, int length)
+        {
+            (_, int actualLength) = range.GetOffsetAndLength(length);
+            return (actualLength == 0);
+        }
     }
 }