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