Switch over to managed Marvin implementation for string hashing (#17029)
[platform/upstream/coreclr.git] / src / mscorlib / src / System / String.Comparison.cs
1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4
5 using System.Collections;
6 using System.Diagnostics;
7 using System.Globalization;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.ConstrainedExecution;
10 using System.Runtime.InteropServices;
11
12 using Internal.Runtime.CompilerServices;
13
14 #if BIT64
15 using nuint = System.UInt64;
16 #else
17 using nuint = System.UInt32;
18 #endif
19
20 namespace System
21 {
22     public partial class String
23     {
24         //
25         //Native Static Methods
26         //
27
28         private static unsafe int CompareOrdinalIgnoreCaseHelper(String strA, String strB)
29         {
30             Debug.Assert(strA != null);
31             Debug.Assert(strB != null);
32             int length = Math.Min(strA.Length, strB.Length);
33
34             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
35             {
36                 char* a = ap;
37                 char* b = bp;
38                 int charA = 0, charB = 0;
39
40                 while (length != 0)
41                 {
42                     charA = *a;
43                     charB = *b;
44
45                     Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
46
47                     // uppercase both chars - notice that we need just one compare per char
48                     if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
49                     if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
50
51                     //Return the (case-insensitive) difference between them.
52                     if (charA != charB)
53                         return charA - charB;
54
55                     // Next char
56                     a++; b++;
57                     length--;
58                 }
59
60                 return strA.Length - strB.Length;
61             }
62         }
63
64         // native call to COMString::CompareOrdinalEx
65         [MethodImplAttribute(MethodImplOptions.InternalCall)]
66         internal static extern int CompareOrdinalHelper(String strA, int indexA, int countA, String strB, int indexB, int countB);
67
68         //This will not work in case-insensitive mode for any character greater than 0x7F.  
69         //We'll throw an ArgumentException.
70         [MethodImplAttribute(MethodImplOptions.InternalCall)]
71         internal static extern unsafe int nativeCompareOrdinalIgnoreCaseWC(String strA, sbyte* strBBytes);
72
73         //
74         //
75         // NATIVE INSTANCE METHODS
76         //
77         //
78
79         //
80         // Search/Query methods
81         //
82
83         [MethodImpl(MethodImplOptions.AggressiveInlining)]
84         private static bool EqualsHelper(String strA, String strB)
85         {
86             Debug.Assert(strA != null);
87             Debug.Assert(strB != null);
88             Debug.Assert(strA.Length == strB.Length);
89
90             return SpanHelpers.SequenceEqual(
91                     ref Unsafe.As<char, byte>(ref strA.GetRawStringData()),
92                     ref Unsafe.As<char, byte>(ref strB.GetRawStringData()),
93                     ((nuint)strA.Length) * 2);
94         }
95
96         private static unsafe bool EqualsIgnoreCaseAsciiHelper(String strA, String strB)
97         {
98             Debug.Assert(strA != null);
99             Debug.Assert(strB != null);
100             Debug.Assert(strA.Length == strB.Length);
101             int length = strA.Length;
102
103             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
104             {
105                 char* a = ap;
106                 char* b = bp;
107
108                 while (length != 0)
109                 {
110                     int charA = *a;
111                     int charB = *b;
112
113                     Debug.Assert((charA | charB) <= 0x7F, "strings have to be ASCII");
114
115                     // Ordinal equals or lowercase equals if the result ends up in the a-z range 
116                     if (charA == charB ||
117                        ((charA | 0x20) == (charB | 0x20) &&
118                           (uint)((charA | 0x20) - 'a') <= (uint)('z' - 'a')))
119                     {
120                         a++;
121                         b++;
122                         length--;
123                     }
124                     else
125                     {
126                         return false;
127                     }
128                 }
129
130                 return true;
131             }
132         }
133
134         private static unsafe int CompareOrdinalHelper(String strA, String strB)
135         {
136             Debug.Assert(strA != null);
137             Debug.Assert(strB != null);
138
139             // NOTE: This may be subject to change if eliminating the check
140             // in the callers makes them small enough to be inlined
141             Debug.Assert(strA._firstChar == strB._firstChar,
142                 "For performance reasons, callers of this method should " +
143                 "check/short-circuit beforehand if the first char is the same.");
144
145             int length = Math.Min(strA.Length, strB.Length);
146
147             fixed (char* ap = &strA._firstChar) fixed (char* bp = &strB._firstChar)
148             {
149                 char* a = ap;
150                 char* b = bp;
151
152                 // Check if the second chars are different here
153                 // The reason we check if _firstChar is different is because
154                 // it's the most common case and allows us to avoid a method call
155                 // to here.
156                 // The reason we check if the second char is different is because
157                 // if the first two chars the same we can increment by 4 bytes,
158                 // leaving us word-aligned on both 32-bit (12 bytes into the string)
159                 // and 64-bit (16 bytes) platforms.
160
161                 // For empty strings, the second char will be null due to padding.
162                 // The start of the string (not including sync block pointer)
163                 // is the method table pointer + string length, which takes up
164                 // 8 bytes on 32-bit, 12 on x64. For empty strings the null
165                 // terminator immediately follows, leaving us with an object
166                 // 10/14 bytes in size. Since everything needs to be a multiple
167                 // of 4/8, this will get padded and zeroed out.
168
169                 // For one-char strings the second char will be the null terminator.
170
171                 // NOTE: If in the future there is a way to read the second char
172                 // without pinning the string (e.g. System.Runtime.CompilerServices.Unsafe
173                 // is exposed to mscorlib, or a future version of C# allows inline IL),
174                 // then do that and short-circuit before the fixed.
175
176                 if (*(a + 1) != *(b + 1)) goto DiffOffset1;
177
178                 // Since we know that the first two chars are the same,
179                 // we can increment by 2 here and skip 4 bytes.
180                 // This leaves us 8-byte aligned, which results
181                 // on better perf for 64-bit platforms.
182                 length -= 2; a += 2; b += 2;
183
184                 // unroll the loop
185 #if BIT64
186                 while (length >= 12)
187                 {
188                     if (*(long*)a != *(long*)b) goto DiffOffset0;
189                     if (*(long*)(a + 4) != *(long*)(b + 4)) goto DiffOffset4;
190                     if (*(long*)(a + 8) != *(long*)(b + 8)) goto DiffOffset8;
191                     length -= 12; a += 12; b += 12;
192                 }
193 #else // BIT64
194                 while (length >= 10)
195                 {
196                     if (*(int*)a != *(int*)b) goto DiffOffset0;
197                     if (*(int*)(a + 2) != *(int*)(b + 2)) goto DiffOffset2;
198                     if (*(int*)(a + 4) != *(int*)(b + 4)) goto DiffOffset4;
199                     if (*(int*)(a + 6) != *(int*)(b + 6)) goto DiffOffset6;
200                     if (*(int*)(a + 8) != *(int*)(b + 8)) goto DiffOffset8;
201                     length -= 10; a += 10; b += 10; 
202                 }
203 #endif // BIT64
204
205                 // Fallback loop:
206                 // go back to slower code path and do comparison on 4 bytes at a time.
207                 // This depends on the fact that the String objects are
208                 // always zero terminated and that the terminating zero is not included
209                 // in the length. For odd string sizes, the last compare will include
210                 // the zero terminator.
211                 while (length > 0)
212                 {
213                     if (*(int*)a != *(int*)b) goto DiffNextInt;
214                     length -= 2;
215                     a += 2;
216                     b += 2;
217                 }
218
219                 // At this point, we have compared all the characters in at least one string.
220                 // The longer string will be larger.
221                 return strA.Length - strB.Length;
222
223 #if BIT64
224             DiffOffset8: a += 4; b += 4;
225             DiffOffset4: a += 4; b += 4;
226 #else // BIT64
227                 // Use jumps instead of falling through, since
228                 // otherwise going to DiffOffset8 will involve
229                 // 8 add instructions before getting to DiffNextInt
230                 DiffOffset8: a += 8; b += 8; goto DiffOffset0;
231                 DiffOffset6: a += 6; b += 6; goto DiffOffset0;
232                 DiffOffset4: a += 2; b += 2;
233                 DiffOffset2: a += 2; b += 2;
234 #endif // BIT64
235
236             DiffOffset0:
237                 // If we reached here, we already see a difference in the unrolled loop above
238 #if BIT64
239                 if (*(int*)a == *(int*)b)
240                 {
241                     a += 2; b += 2;
242                 }
243 #endif // BIT64
244
245             DiffNextInt:
246                 if (*a != *b) return *a - *b;
247
248                 DiffOffset1:
249                 Debug.Assert(*(a + 1) != *(b + 1), "This char must be different if we reach here!");
250                 return *(a + 1) - *(b + 1);
251             }
252         }
253
254         // Provides a culture-correct string comparison. StrA is compared to StrB
255         // to determine whether it is lexicographically less, equal, or greater, and then returns
256         // either a negative integer, 0, or a positive integer; respectively.
257         //
258         public static int Compare(String strA, String strB)
259         {
260             return Compare(strA, strB, StringComparison.CurrentCulture);
261         }
262
263
264         // Provides a culture-correct string comparison. strA is compared to strB
265         // to determine whether it is lexicographically less, equal, or greater, and then a
266         // negative integer, 0, or a positive integer is returned; respectively.
267         // The case-sensitive option is set by ignoreCase
268         //
269         public static int Compare(String strA, String strB, bool ignoreCase)
270         {
271             var comparisonType = ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture;
272             return Compare(strA, strB, comparisonType);
273         }
274
275
276         // Provides a more flexible function for string comparision. See StringComparison 
277         // for meaning of different comparisonType.
278         public static int Compare(String strA, String strB, StringComparison comparisonType)
279         {
280             if (object.ReferenceEquals(strA, strB))
281             {
282                 CheckStringComparison(comparisonType);
283                 return 0;
284             }
285
286             // They can't both be null at this point.
287             if (strA == null)
288             {
289                 CheckStringComparison(comparisonType);
290                 return -1;
291             }
292             if (strB == null)
293             {
294                 CheckStringComparison(comparisonType);
295                 return 1;
296             }
297
298             switch (comparisonType)
299             {
300                 case StringComparison.CurrentCulture:
301                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.None);
302
303                 case StringComparison.CurrentCultureIgnoreCase:
304                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, strB, CompareOptions.IgnoreCase);
305
306                 case StringComparison.InvariantCulture:
307                     return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.None);
308
309                 case StringComparison.InvariantCultureIgnoreCase:
310                     return CompareInfo.Invariant.Compare(strA, strB, CompareOptions.IgnoreCase);
311
312                 case StringComparison.Ordinal:
313                     // Most common case: first character is different.
314                     // Returns false for empty strings.
315                     if (strA._firstChar != strB._firstChar)
316                     {
317                         return strA._firstChar - strB._firstChar;
318                     }
319
320                     return CompareOrdinalHelper(strA, strB);
321
322                 case StringComparison.OrdinalIgnoreCase:
323                     // If both strings are ASCII strings, we can take the fast path.
324                     if (strA.IsAscii() && strB.IsAscii())
325                     {
326                         return (CompareOrdinalIgnoreCaseHelper(strA, strB));
327                     }
328
329                     return CompareInfo.CompareOrdinalIgnoreCase(strA, 0, strA.Length, strB, 0, strB.Length);
330
331                 default:
332                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
333             }
334         }
335
336
337         // Provides a culture-correct string comparison. strA is compared to strB
338         // to determine whether it is lexicographically less, equal, or greater, and then a
339         // negative integer, 0, or a positive integer is returned; respectively.
340         //
341         public static int Compare(String strA, String strB, CultureInfo culture, CompareOptions options)
342         {
343             if (culture == null)
344             {
345                 throw new ArgumentNullException(nameof(culture));
346             }
347
348             return culture.CompareInfo.Compare(strA, strB, options);
349         }
350
351
352
353         // Provides a culture-correct string comparison. strA is compared to strB
354         // to determine whether it is lexicographically less, equal, or greater, and then a
355         // negative integer, 0, or a positive integer is returned; respectively.
356         // The case-sensitive option is set by ignoreCase, and the culture is set
357         // by culture
358         //
359         public static int Compare(String strA, String strB, bool ignoreCase, CultureInfo culture)
360         {
361             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
362             return Compare(strA, strB, culture, options);
363         }
364
365         // Determines whether two string regions match.  The substring of strA beginning
366         // at indexA of length count is compared with the substring of strB
367         // beginning at indexB of the same length.
368         //
369         public static int Compare(String strA, int indexA, String strB, int indexB, int length)
370         {
371             // NOTE: It's important we call the boolean overload, and not the StringComparison
372             // one. The two have some subtly different behavior (see notes in the former).
373             return Compare(strA, indexA, strB, indexB, length, ignoreCase: false);
374         }
375
376         // Determines whether two string regions match.  The substring of strA beginning
377         // at indexA of length count is compared with the substring of strB
378         // beginning at indexB of the same length.  Case sensitivity is determined by the ignoreCase boolean.
379         //
380         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase)
381         {
382             // Ideally we would just forward to the string.Compare overload that takes
383             // a StringComparison parameter, and just pass in CurrentCulture/CurrentCultureIgnoreCase.
384             // That function will return early if an optimization can be applied, e.g. if
385             // (object)strA == strB && indexA == indexB then it will return 0 straightaway.
386             // There are a couple of subtle behavior differences that prevent us from doing so
387             // however:
388             // - string.Compare(null, -1, null, -1, -1, StringComparison.CurrentCulture) works
389             //   since that method also returns early for nulls before validation. It shouldn't
390             //   for this overload.
391             // - Since we originally forwarded to CompareInfo.Compare for all of the argument
392             //   validation logic, the ArgumentOutOfRangeExceptions thrown will contain different
393             //   parameter names.
394             // Therefore, we have to duplicate some of the logic here.
395
396             int lengthA = length;
397             int lengthB = length;
398
399             if (strA != null)
400             {
401                 lengthA = Math.Min(lengthA, strA.Length - indexA);
402             }
403
404             if (strB != null)
405             {
406                 lengthB = Math.Min(lengthB, strB.Length - indexB);
407             }
408
409             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
410             return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
411         }
412
413         // Determines whether two string regions match.  The substring of strA beginning
414         // at indexA of length length is compared with the substring of strB
415         // beginning at indexB of the same length.  Case sensitivity is determined by the ignoreCase boolean,
416         // and the culture is set by culture.
417         //
418         public static int Compare(String strA, int indexA, String strB, int indexB, int length, bool ignoreCase, CultureInfo culture)
419         {
420             var options = ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None;
421             return Compare(strA, indexA, strB, indexB, length, culture, options);
422         }
423
424
425         // Determines whether two string regions match.  The substring of strA beginning
426         // at indexA of length length is compared with the substring of strB
427         // beginning at indexB of the same length.
428         //
429         public static int Compare(String strA, int indexA, String strB, int indexB, int length, CultureInfo culture, CompareOptions options)
430         {
431             if (culture == null)
432             {
433                 throw new ArgumentNullException(nameof(culture));
434             }
435
436             int lengthA = length;
437             int lengthB = length;
438
439             if (strA != null)
440             {
441                 lengthA = Math.Min(lengthA, strA.Length - indexA);
442             }
443
444             if (strB != null)
445             {
446                 lengthB = Math.Min(lengthB, strB.Length - indexB);
447             }
448
449             return culture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, options);
450         }
451
452         public static int Compare(String strA, int indexA, String strB, int indexB, int length, StringComparison comparisonType)
453         {
454             CheckStringComparison(comparisonType);
455
456             if (strA == null || strB == null)
457             {
458
459                 if (object.ReferenceEquals(strA, strB))
460                 {
461                     // They're both null
462                     return 0;
463                 }
464
465                 return strA == null ? -1 : 1;
466             }
467
468             if (length < 0)
469             {
470                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeLength);
471             }
472
473             if (indexA < 0 || indexB < 0)
474             {
475                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
476                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
477             }
478
479             if (strA.Length - indexA < 0 || strB.Length - indexB < 0)
480             {
481                 string paramName = strA.Length - indexA < 0 ? nameof(indexA) : nameof(indexB);
482                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
483             }
484
485             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
486             {
487                 return 0;
488             }
489
490             int lengthA = Math.Min(length, strA.Length - indexA);
491             int lengthB = Math.Min(length, strB.Length - indexB);
492
493             switch (comparisonType)
494             {
495                 case StringComparison.CurrentCulture:
496                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
497
498                 case StringComparison.CurrentCultureIgnoreCase:
499                     return CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
500
501                 case StringComparison.InvariantCulture:
502                     return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None);
503
504                 case StringComparison.InvariantCultureIgnoreCase:
505                     return CompareInfo.Invariant.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase);
506
507                 case StringComparison.Ordinal:
508                     return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
509
510                 case StringComparison.OrdinalIgnoreCase:
511                     return (CompareInfo.CompareOrdinalIgnoreCase(strA, indexA, lengthA, strB, indexB, lengthB));
512
513                 default:
514                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
515             }
516         }
517
518         // Compares strA and strB using an ordinal (code-point) comparison.
519         //
520         public static int CompareOrdinal(String strA, String strB)
521         {
522             if (object.ReferenceEquals(strA, strB))
523             {
524                 return 0;
525             }
526
527             // They can't both be null at this point.
528             if (strA == null)
529             {
530                 return -1;
531             }
532             if (strB == null)
533             {
534                 return 1;
535             }
536
537             // Most common case, first character is different.
538             // This will return false for empty strings.
539             if (strA._firstChar != strB._firstChar)
540             {
541                 return strA._firstChar - strB._firstChar;
542             }
543
544             return CompareOrdinalHelper(strA, strB);
545         }
546
547         internal static int CompareOrdinal(ReadOnlySpan<char> strA, ReadOnlySpan<char> strB)
548         {
549             // TODO: Add a vectorized code path, similar to SequenceEqual
550             // https://github.com/dotnet/corefx/blob/master/src/System.Memory/src/System/SpanHelpers.byte.cs#L900
551
552             int minLength = Math.Min(strA.Length, strB.Length);
553             ref char first = ref MemoryMarshal.GetReference(strA);
554             ref char second = ref MemoryMarshal.GetReference(strB);
555
556             int i = 0;
557             if (minLength >= sizeof(nuint) / sizeof(char))
558             {
559                 while (i < minLength - sizeof(nuint) / sizeof(char))
560                 {
561                     if (Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref first, i))) !=
562                         Unsafe.ReadUnaligned<nuint>(ref Unsafe.As<char, byte>(ref Unsafe.Add(ref second, i))))
563                     {
564                         break;
565                     }
566                     i += sizeof(nuint) / sizeof(char);
567                 }
568             }
569             while (i < minLength)
570             {
571                 char a = Unsafe.Add(ref first, i);
572                 char b = Unsafe.Add(ref second, i);
573                 if (a != b)
574                 {
575                     return a - b;
576                 }
577                 i++;
578             }
579             return strA.Length - strB.Length;
580         }
581
582         // Compares strA and strB using an ordinal (code-point) comparison.
583         //
584         public static int CompareOrdinal(String strA, int indexA, String strB, int indexB, int length)
585         {
586             if (strA == null || strB == null)
587             {
588                 if (object.ReferenceEquals(strA, strB))
589                 {
590                     // They're both null
591                     return 0;
592                 }
593
594                 return strA == null ? -1 : 1;
595             }
596
597             // COMPAT: Checking for nulls should become before the arguments are validated,
598             // but other optimizations which allow us to return early should come after.
599
600             if (length < 0)
601             {
602                 throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NegativeCount);
603             }
604
605             if (indexA < 0 || indexB < 0)
606             {
607                 string paramName = indexA < 0 ? nameof(indexA) : nameof(indexB);
608                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
609             }
610
611             int lengthA = Math.Min(length, strA.Length - indexA);
612             int lengthB = Math.Min(length, strB.Length - indexB);
613
614             if (lengthA < 0 || lengthB < 0)
615             {
616                 string paramName = lengthA < 0 ? nameof(indexA) : nameof(indexB);
617                 throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_Index);
618             }
619
620             if (length == 0 || (object.ReferenceEquals(strA, strB) && indexA == indexB))
621             {
622                 return 0;
623             }
624
625             return CompareOrdinalHelper(strA, indexA, lengthA, strB, indexB, lengthB);
626         }
627
628         // Compares this String to another String (cast as object), returning an integer that
629         // indicates the relationship. This method returns a value less than 0 if this is less than value, 0
630         // if this is equal to value, or a value greater than 0 if this is greater than value.
631         //
632         public int CompareTo(Object value)
633         {
634             if (value == null)
635             {
636                 return 1;
637             }
638
639             string other = value as string;
640
641             if (other == null)
642             {
643                 throw new ArgumentException(SR.Arg_MustBeString);
644             }
645
646             return CompareTo(other); // will call the string-based overload
647         }
648
649         // Determines the sorting relation of StrB to the current instance.
650         //
651         public int CompareTo(String strB)
652         {
653             return string.Compare(this, strB, StringComparison.CurrentCulture);
654         }
655
656         // Determines whether a specified string is a suffix of the current instance.
657         //
658         // The case-sensitive and culture-sensitive option is set by options,
659         // and the default culture is used.
660         //        
661         public Boolean EndsWith(String value)
662         {
663             return EndsWith(value, StringComparison.CurrentCulture);
664         }
665
666         public Boolean EndsWith(String value, StringComparison comparisonType)
667         {
668             if ((Object)value == null)
669             {
670                 throw new ArgumentNullException(nameof(value));
671             }
672             
673             if ((Object)this == (Object)value)
674             {
675                 CheckStringComparison(comparisonType);
676                 return true;
677             }
678
679             if (value.Length == 0)
680             {
681                 CheckStringComparison(comparisonType);
682                 return true;
683             }
684
685             switch (comparisonType)
686             {
687                 case StringComparison.CurrentCulture:
688                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.None);
689
690                 case StringComparison.CurrentCultureIgnoreCase:
691                     return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(this, value, CompareOptions.IgnoreCase);
692
693                 case StringComparison.InvariantCulture:
694                     return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.None);
695
696                 case StringComparison.InvariantCultureIgnoreCase:
697                     return CompareInfo.Invariant.IsSuffix(this, value, CompareOptions.IgnoreCase);
698
699                 case StringComparison.Ordinal:
700                     return this.Length < value.Length ? false : (CompareOrdinalHelper(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
701
702                 case StringComparison.OrdinalIgnoreCase:
703                     return this.Length < value.Length ? false : (CompareInfo.CompareOrdinalIgnoreCase(this, this.Length - value.Length, value.Length, value, 0, value.Length) == 0);
704
705                 default:
706                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
707             }
708         }
709
710         public Boolean EndsWith(String value, Boolean ignoreCase, CultureInfo culture)
711         {
712             if (null == value)
713             {
714                 throw new ArgumentNullException(nameof(value));
715             }
716
717             if ((object)this == (object)value)
718             {
719                 return true;
720             }
721
722             CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
723             return referenceCulture.CompareInfo.IsSuffix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
724         }
725
726         public bool EndsWith(char value)
727         {
728             int thisLen = Length;
729             return thisLen != 0 && this[thisLen - 1] == value;
730         }
731
732         // Determines whether two strings match.
733         public override bool Equals(Object obj)
734         {
735             if (object.ReferenceEquals(this, obj))
736                 return true;
737
738             string str = obj as string;
739             if (str == null)
740                 return false;
741
742             if (this.Length != str.Length)
743                 return false;
744
745             return EqualsHelper(this, str);
746         }
747
748         // Determines whether two strings match.
749         public bool Equals(String value)
750         {
751             if (object.ReferenceEquals(this, value))
752                 return true;
753
754             // NOTE: No need to worry about casting to object here.
755             // If either side of an == comparison between strings
756             // is null, Roslyn generates a simple ceq instruction
757             // instead of calling string.op_Equality.
758             if (value == null)
759                 return false;
760
761             if (this.Length != value.Length)
762                 return false;
763
764             return EqualsHelper(this, value);
765         }
766
767         public bool Equals(String value, StringComparison comparisonType)
768         {
769             if ((Object)this == (Object)value)
770             {
771                 CheckStringComparison(comparisonType);
772                 return true;
773             }
774
775             if ((Object)value == null)
776             {
777                 CheckStringComparison(comparisonType);
778                 return false;
779             }
780
781             switch (comparisonType)
782             {
783                 case StringComparison.CurrentCulture:
784                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.None) == 0);
785
786                 case StringComparison.CurrentCultureIgnoreCase:
787                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(this, value, CompareOptions.IgnoreCase) == 0);
788
789                 case StringComparison.InvariantCulture:
790                     return (CompareInfo.Invariant.Compare(this, value, CompareOptions.None) == 0);
791
792                 case StringComparison.InvariantCultureIgnoreCase:
793                     return (CompareInfo.Invariant.Compare(this, value, CompareOptions.IgnoreCase) == 0);
794
795                 case StringComparison.Ordinal:
796                     if (this.Length != value.Length)
797                         return false;
798                     return EqualsHelper(this, value);
799
800                 case StringComparison.OrdinalIgnoreCase:
801                     if (this.Length != value.Length)
802                         return false;
803
804                     // If both strings are ASCII strings, we can take the fast path.
805                     if (this.IsAscii() && value.IsAscii())
806                     {
807                         return EqualsIgnoreCaseAsciiHelper(this, value);
808                     }
809
810                     return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, this.Length, value, 0, value.Length) == 0);
811
812                 default:
813                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
814             }
815         }
816
817
818         // Determines whether two Strings match.
819         public static bool Equals(String a, String b)
820         {
821             if ((Object)a == (Object)b)
822             {
823                 return true;
824             }
825
826             if ((Object)a == null || (Object)b == null || a.Length != b.Length)
827             {
828                 return false;
829             }
830
831             return EqualsHelper(a, b);
832         }
833
834         public static bool Equals(String a, String b, StringComparison comparisonType)
835         {
836             if ((Object)a == (Object)b)
837             {
838                 CheckStringComparison(comparisonType);
839                 return true;
840             }
841
842             if ((Object)a == null || (Object)b == null)
843             {
844                 CheckStringComparison(comparisonType);
845                 return false;
846             }
847
848             switch (comparisonType)
849             {
850                 case StringComparison.CurrentCulture:
851                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.None) == 0);
852
853                 case StringComparison.CurrentCultureIgnoreCase:
854                     return (CultureInfo.CurrentCulture.CompareInfo.Compare(a, b, CompareOptions.IgnoreCase) == 0);
855
856                 case StringComparison.InvariantCulture:
857                     return (CompareInfo.Invariant.Compare(a, b, CompareOptions.None) == 0);
858
859                 case StringComparison.InvariantCultureIgnoreCase:
860                     return (CompareInfo.Invariant.Compare(a, b, CompareOptions.IgnoreCase) == 0);
861
862                 case StringComparison.Ordinal:
863                     if (a.Length != b.Length)
864                         return false;
865
866                     return EqualsHelper(a, b);
867
868                 case StringComparison.OrdinalIgnoreCase:
869                     if (a.Length != b.Length)
870                         return false;
871                     else
872                     {
873                         // If both strings are ASCII strings, we can take the fast path.
874                         if (a.IsAscii() && b.IsAscii())
875                         {
876                             return EqualsIgnoreCaseAsciiHelper(a, b);
877                         }
878                         // Take the slow path.
879
880                         return (CompareInfo.CompareOrdinalIgnoreCase(a, 0, a.Length, b, 0, b.Length) == 0);
881                     }
882
883                 default:
884                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
885             }
886         }
887
888         public static bool operator ==(String a, String b)
889         {
890             return String.Equals(a, b);
891         }
892
893         public static bool operator !=(String a, String b)
894         {
895             return !String.Equals(a, b);
896         }
897
898         // Gets a hash code for this string.  If strings A and B are such that A.Equals(B), then
899         // they will return the same hash code.
900         public override int GetHashCode()
901         {
902             return Marvin.ComputeHash32(ref Unsafe.As<char, byte>(ref _firstChar), _stringLength * 2, Marvin.DefaultSeed);
903         }
904
905         // Gets a hash code for this string and this comparison. If strings A and B and comparison C are such
906         // that String.Equals(A, B, C), then they will return the same hash code with this comparison C.
907         public int GetHashCode(StringComparison comparisonType) => StringComparer.FromComparison(comparisonType).GetHashCode(this);
908
909         // Use this if and only if you need the hashcode to not change across app domains (e.g. you have an app domain agile
910         // hash table).
911         internal int GetLegacyNonRandomizedHashCode()
912         {
913             unsafe
914             {
915                 fixed (char* src = &_firstChar)
916                 {
917                     Debug.Assert(src[this.Length] == '\0', "src[this.Length] == '\\0'");
918                     Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
919 #if BIT64
920                     int hash1 = 5381;
921 #else // !BIT64 (32)
922                     int hash1 = (5381<<16) + 5381;
923 #endif
924                     int hash2 = hash1;
925
926 #if BIT64
927                     int c;
928                     char* s = src;
929                     while ((c = s[0]) != 0)
930                     {
931                         hash1 = ((hash1 << 5) + hash1) ^ c;
932                         c = s[1];
933                         if (c == 0)
934                             break;
935                         hash2 = ((hash2 << 5) + hash2) ^ c;
936                         s += 2;
937                     }
938 #else // !BIT64 (32)
939                     // 32 bit machines.
940                     int* pint = (int *)src;
941                     int len = this.Length;
942                     while (len > 2)
943                     {
944                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
945                         hash2 = ((hash2 << 5) + hash2 + (hash2 >> 27)) ^ pint[1];
946                         pint += 2;
947                         len  -= 4;
948                     }
949
950                     if (len > 0)
951                     {
952                         hash1 = ((hash1 << 5) + hash1 + (hash1 >> 27)) ^ pint[0];
953                     }
954 #endif
955 #if DEBUG
956                     // We want to ensure we can change our hash function daily.
957                     // This is perfectly fine as long as you don't persist the
958                     // value from GetHashCode to disk or count on String A 
959                     // hashing before string B.  Those are bugs in your code.
960                     hash1 ^= ThisAssembly.DailyBuildNumber;
961 #endif
962                     return hash1 + (hash2 * 1566083941);
963                 }
964             }
965         }
966
967         // Determines whether a specified string is a prefix of the current instance
968         //
969         public Boolean StartsWith(String value)
970         {
971             if ((Object)value == null)
972             {
973                 throw new ArgumentNullException(nameof(value));
974             }
975             return StartsWith(value, StringComparison.CurrentCulture);
976         }
977
978         public Boolean StartsWith(String value, StringComparison comparisonType)
979         {
980             if ((Object)value == null)
981             {
982                 throw new ArgumentNullException(nameof(value));
983             }
984
985             if ((Object)this == (Object)value)
986             {
987                 CheckStringComparison(comparisonType);
988                 return true;
989             }
990
991             if (value.Length == 0)
992             {
993                 CheckStringComparison(comparisonType);
994                 return true;
995             }
996
997             switch (comparisonType)
998             {
999                 case StringComparison.CurrentCulture:
1000                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
1001
1002                 case StringComparison.CurrentCultureIgnoreCase:
1003                     return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
1004
1005                 case StringComparison.InvariantCulture:
1006                     return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.None);
1007
1008                 case StringComparison.InvariantCultureIgnoreCase:
1009                     return CompareInfo.Invariant.IsPrefix(this, value, CompareOptions.IgnoreCase);
1010
1011                 case StringComparison.Ordinal:
1012                     if (this.Length < value.Length || _firstChar != value._firstChar)
1013                     {
1014                         return false;
1015                     }
1016                     return (value.Length == 1) ?
1017                             true :                 // First char is the same and thats all there is to compare
1018                             SpanHelpers.SequenceEqual(
1019                                 ref Unsafe.As<char, byte>(ref this.GetRawStringData()),
1020                                 ref Unsafe.As<char, byte>(ref value.GetRawStringData()),
1021                                 ((nuint)value.Length) * 2);
1022
1023                 case StringComparison.OrdinalIgnoreCase:
1024                     if (this.Length < value.Length)
1025                     {
1026                         return false;
1027                     }
1028                     return (CompareInfo.CompareOrdinalIgnoreCase(this, 0, value.Length, value, 0, value.Length) == 0);
1029
1030                 default:
1031                     throw new ArgumentException(SR.NotSupported_StringComparison, nameof(comparisonType));
1032             }
1033         }
1034
1035         public Boolean StartsWith(String value, Boolean ignoreCase, CultureInfo culture)
1036         {
1037             if (null == value)
1038             {
1039                 throw new ArgumentNullException(nameof(value));
1040             }
1041
1042             if ((object)this == (object)value)
1043             {
1044                 return true;
1045             }
1046
1047             CultureInfo referenceCulture = culture ?? CultureInfo.CurrentCulture;
1048             return referenceCulture.CompareInfo.IsPrefix(this, value, ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
1049         }
1050
1051         public bool StartsWith(char value) => Length != 0 && _firstChar == value;
1052
1053         internal static void CheckStringComparison(StringComparison comparisonType)
1054         {
1055             // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase]
1056             if ((uint)(comparisonType - StringComparison.CurrentCulture) > (StringComparison.OrdinalIgnoreCase - StringComparison.CurrentCulture))
1057             {
1058                 ThrowHelper.ThrowArgumentException(ExceptionResource.NotSupported_StringComparison, ExceptionArgument.comparisonType);
1059             }
1060         }
1061     }
1062 }