Switch over to managed Marvin implementation for string hashing (#17029)
[platform/upstream/coreclr.git] / src / mscorlib / src / System / Globalization / CompareInfo.Unix.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.Buffers;
6 using System.Diagnostics;
7 using System.Runtime.CompilerServices;
8 using System.Runtime.InteropServices;
9 using System.Security;
10
11 namespace System.Globalization
12 {
13     public partial class CompareInfo
14     {
15         [NonSerialized]
16         private Interop.Globalization.SafeSortHandle _sortHandle;
17
18         [NonSerialized]
19         private bool _isAsciiEqualityOrdinal;
20
21         private void InitSort(CultureInfo culture)
22         {
23             _sortName = culture.SortName;
24
25             if (_invariantMode)
26             {
27                 _isAsciiEqualityOrdinal = true;
28             }
29             else
30             {
31                 Interop.Globalization.ResultCode resultCode = Interop.Globalization.GetSortHandle(GetNullTerminatedUtf8String(_sortName), out _sortHandle);
32                 if (resultCode != Interop.Globalization.ResultCode.Success)
33                 {
34                     _sortHandle.Dispose();
35
36                     if (resultCode == Interop.Globalization.ResultCode.OutOfMemory)
37                         throw new OutOfMemoryException();
38
39                     throw new ExternalException(SR.Arg_ExternalException);
40                 }
41                 _isAsciiEqualityOrdinal = (_sortName == "en-US" || _sortName == "");
42             }
43         }
44
45         internal static unsafe int IndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
46         {
47             Debug.Assert(!GlobalizationMode.Invariant);
48
49             Debug.Assert(source != null);
50             Debug.Assert(value != null);
51
52             if (value.Length == 0)
53             {
54                 return startIndex;
55             }
56
57             if (count < value.Length)
58             {
59                 return -1;
60             }
61
62             if (ignoreCase)
63             {
64                 fixed (char* pSource = source)
65                 {
66                     int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + startIndex, count, findLast: false);
67                     return index != -1 ?
68                         startIndex + index :
69                         -1;
70                 }
71             }
72
73             int endIndex = startIndex + (count - value.Length);
74             for (int i = startIndex; i <= endIndex; i++)
75             {
76                 int valueIndex, sourceIndex;
77
78                 for (valueIndex = 0, sourceIndex = i;
79                      valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
80                      valueIndex++, sourceIndex++) ;
81
82                 if (valueIndex == value.Length)
83                 {
84                     return i;
85                 }
86             }
87
88             return -1;
89         }
90
91         internal static unsafe int IndexOfOrdinalCore(ReadOnlySpan<char> source, ReadOnlySpan<char> value, bool ignoreCase)
92         {
93             Debug.Assert(!GlobalizationMode.Invariant);
94
95             Debug.Assert(source.Length != 0);
96             Debug.Assert(value.Length != 0);
97
98             if (source.Length < value.Length)
99             {
100                 return -1;
101             }
102
103             if (ignoreCase)
104             {
105                 fixed (char* pSource = &MemoryMarshal.GetReference(source))
106                 fixed (char* pValue = &MemoryMarshal.GetReference(value))
107                 {
108                     int index = Interop.Globalization.IndexOfOrdinalIgnoreCase(pValue, value.Length, pSource, source.Length, findLast: false);
109                     return index;
110                 }
111             }
112
113             int endIndex = source.Length - value.Length;
114             for (int i = 0; i <= endIndex; i++)
115             {
116                 int valueIndex, sourceIndex;
117
118                 for (valueIndex = 0, sourceIndex = i;
119                      valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
120                      valueIndex++, sourceIndex++)
121                     ;
122
123                 if (valueIndex == value.Length)
124                 {
125                     return i;
126                 }
127             }
128
129             return -1;
130         }
131
132         internal static unsafe int LastIndexOfOrdinalCore(string source, string value, int startIndex, int count, bool ignoreCase)
133         {
134             Debug.Assert(!GlobalizationMode.Invariant);
135
136             Debug.Assert(source != null);
137             Debug.Assert(value != null);
138
139             if (value.Length == 0)
140             {
141                 return startIndex;
142             }
143
144             if (count < value.Length)
145             {
146                 return -1;
147             }
148
149             // startIndex is the index into source where we start search backwards from. 
150             // leftStartIndex is the index into source of the start of the string that is 
151             // count characters away from startIndex.
152             int leftStartIndex = startIndex - count + 1;
153
154             if (ignoreCase)
155             {
156                 fixed (char* pSource = source)
157                 {
158                     int lastIndex = Interop.Globalization.IndexOfOrdinalIgnoreCase(value, value.Length, pSource + leftStartIndex, count, findLast: true);
159                     return lastIndex != -1 ?
160                         leftStartIndex + lastIndex :
161                         -1;
162                 }
163             }
164
165             for (int i = startIndex - value.Length + 1; i >= leftStartIndex; i--)
166             {
167                 int valueIndex, sourceIndex;
168
169                 for (valueIndex = 0, sourceIndex = i;
170                      valueIndex < value.Length && source[sourceIndex] == value[valueIndex];
171                      valueIndex++, sourceIndex++) ;
172
173                 if (valueIndex == value.Length) {
174                     return i;
175                 }
176             }
177
178             return -1;
179         }
180
181         private static unsafe int CompareStringOrdinalIgnoreCase(char* string1, int count1, char* string2, int count2)
182         {
183             Debug.Assert(!GlobalizationMode.Invariant);
184
185             return Interop.Globalization.CompareStringOrdinalIgnoreCase(string1, count1, string2, count2);
186         }
187
188         // TODO https://github.com/dotnet/coreclr/issues/13827:
189         // This method shouldn't be necessary, as we should be able to just use the overload
190         // that takes two spans.  But due to this issue, that's adding significant overhead.
191         private unsafe int CompareString(ReadOnlySpan<char> string1, string string2, CompareOptions options)
192         {
193             Debug.Assert(!_invariantMode);
194             Debug.Assert(string2 != null);
195             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
196
197             fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
198             fixed (char* pString2 = &string2.GetRawStringData())
199             {
200                 return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
201             }
202         }
203
204         private unsafe int CompareString(ReadOnlySpan<char> string1, ReadOnlySpan<char> string2, CompareOptions options)
205         {
206             Debug.Assert(!_invariantMode);
207             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
208
209             fixed (char* pString1 = &MemoryMarshal.GetReference(string1))
210             fixed (char* pString2 = &MemoryMarshal.GetReference(string2))
211             {
212                 return Interop.Globalization.CompareString(_sortHandle, pString1, string1.Length, pString2, string2.Length, options);
213             }
214         }
215
216         internal unsafe int IndexOfCore(string source, string target, int startIndex, int count, CompareOptions options, int* matchLengthPtr)
217         {
218             Debug.Assert(!_invariantMode);
219
220             Debug.Assert(!string.IsNullOrEmpty(source));
221             Debug.Assert(target != null);
222             Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
223
224             int index;
225
226             if (target.Length == 0)
227             {
228                 if (matchLengthPtr != null)
229                     *matchLengthPtr = 0;
230                 return startIndex;
231             }
232
233             if (options == CompareOptions.Ordinal)
234             {
235                 index = IndexOfOrdinal(source, target, startIndex, count, ignoreCase: false);
236                 if (index != -1)
237                 {
238                     if (matchLengthPtr != null)
239                         *matchLengthPtr = target.Length;
240                 }
241                 return index;
242             }
243
244             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
245             {
246                 index = IndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
247                 if (index != -1)
248                 {
249                     if (matchLengthPtr != null)
250                         *matchLengthPtr = target.Length;
251                 }
252                 return index;
253             }
254
255             fixed (char* pSource = source)
256             {
257                 index = Interop.Globalization.IndexOf(_sortHandle, target, target.Length, pSource + startIndex, count, options, matchLengthPtr);
258
259                 return index != -1 ? index + startIndex : -1;
260             }
261         }
262
263         // For now, this method is only called from Span APIs with either options == CompareOptions.None or CompareOptions.IgnoreCase
264         internal unsafe int IndexOfCore(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
265         {
266             Debug.Assert(!_invariantMode);
267             Debug.Assert(source.Length != 0);
268             Debug.Assert(target.Length != 0);
269
270             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
271             {
272                 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
273                 {
274                     return IndexOfOrdinalIgnoreCaseHelper(source, target, options, matchLengthPtr);
275                 }
276                 else
277                 {
278                     return IndexOfOrdinalHelper(source, target, options, matchLengthPtr);
279                 }
280             }
281             else
282             {
283                 fixed (char* pSource = &MemoryMarshal.GetReference(source))
284                 fixed (char* pTarget = &MemoryMarshal.GetReference(target))
285                 {
286                     return Interop.Globalization.IndexOf(_sortHandle, pTarget, target.Length, pSource, source.Length, options, matchLengthPtr);
287                 }
288             }
289         }
290
291         private unsafe int IndexOfOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
292         {
293             Debug.Assert(!_invariantMode);
294
295             Debug.Assert(!source.IsEmpty);
296             Debug.Assert(!target.IsEmpty);
297             Debug.Assert(_isAsciiEqualityOrdinal);
298
299             fixed (char* ap = &MemoryMarshal.GetReference(source))
300             fixed (char* bp = &MemoryMarshal.GetReference(target))
301             {
302                 char* a = ap;
303                 char* b = bp;
304                 int endIndex = source.Length - target.Length;
305
306                 if (endIndex < 0)
307                     goto InteropCall;
308
309                 for (int j = 0; j < target.Length; j++)
310                 {
311                     char targetChar = *(b + j);
312                     if (targetChar >= 0x80 || s_highCharTable[targetChar])
313                         goto InteropCall;
314                 }
315
316                 int i = 0;
317                 for (; i <= endIndex; i++)
318                 {
319                     int targetIndex = 0;
320                     int sourceIndex = i;
321
322                     for (; targetIndex < target.Length; targetIndex++)
323                     {
324                         char valueChar = *(a + sourceIndex);
325                         char targetChar = *(b + targetIndex);
326
327                         if (valueChar == targetChar && valueChar < 0x80 && !s_highCharTable[valueChar])
328                         {
329                             sourceIndex++;
330                             continue;
331                         }
332
333                         // uppercase both chars - notice that we need just one compare per char
334                         if ((uint)(valueChar - 'a') <= ('z' - 'a'))
335                             valueChar = (char)(valueChar - 0x20);
336                         if ((uint)(targetChar - 'a') <= ('z' - 'a'))
337                             targetChar = (char)(targetChar - 0x20);
338
339                         if (valueChar >= 0x80 || s_highCharTable[valueChar])
340                             goto InteropCall;
341                         else if (valueChar != targetChar)
342                             break;
343                         sourceIndex++;
344                     }
345
346                     if (targetIndex == target.Length)
347                     {
348                         if (matchLengthPtr != null)
349                             *matchLengthPtr = target.Length;
350                         return i;
351                     }
352                 }
353                 if (i > endIndex)
354                     return -1;
355             InteropCall:
356                 return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
357             }
358         }
359
360         private unsafe int IndexOfOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> target, CompareOptions options, int* matchLengthPtr)
361         {
362             Debug.Assert(!_invariantMode);
363
364             Debug.Assert(!source.IsEmpty);
365             Debug.Assert(!target.IsEmpty);
366             Debug.Assert(_isAsciiEqualityOrdinal);
367
368             fixed (char* ap = &MemoryMarshal.GetReference(source))
369             fixed (char* bp = &MemoryMarshal.GetReference(target))
370             {
371                 char* a = ap;
372                 char* b = bp;
373                 int endIndex = source.Length - target.Length;
374
375                 if (endIndex < 0)
376                     goto InteropCall;
377
378                 for (int j = 0; j < target.Length; j++)
379                 {
380                     char targetChar = *(b + j);
381                     if (targetChar >= 0x80 || s_highCharTable[targetChar])
382                         goto InteropCall;
383                 }
384
385                 int i = 0;
386                 for (; i <= endIndex; i++)
387                 {
388                     int targetIndex = 0;
389                     int sourceIndex = i;
390
391                     for (; targetIndex < target.Length; targetIndex++)
392                     {
393                         char valueChar = *(a + sourceIndex);
394                         char targetChar = *(b + targetIndex);
395                         if (valueChar >= 0x80 || s_highCharTable[valueChar])
396                             goto InteropCall;
397                         else if (valueChar != targetChar)
398                             break;
399                         sourceIndex++;
400                     }
401
402                     if (targetIndex == target.Length)
403                     {
404                         if (matchLengthPtr != null)
405                             *matchLengthPtr = target.Length;
406                         return i;
407                     }
408                 }
409                 if (i > endIndex)
410                     return -1;
411             InteropCall:
412                 return Interop.Globalization.IndexOf(_sortHandle, b, target.Length, a, source.Length, options, matchLengthPtr);
413             }
414         }
415
416         private unsafe int LastIndexOfCore(string source, string target, int startIndex, int count, CompareOptions options)
417         {
418             Debug.Assert(!_invariantMode);
419
420             Debug.Assert(!string.IsNullOrEmpty(source));
421             Debug.Assert(target != null);
422             Debug.Assert((options & CompareOptions.OrdinalIgnoreCase) == 0);
423
424             if (target.Length == 0)
425             {
426                 return startIndex;
427             }
428
429             if (options == CompareOptions.Ordinal)
430             {
431                 return LastIndexOfOrdinalCore(source, target, startIndex, count, ignoreCase: false);
432             }
433
434             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && target.IsFastSort())
435             {
436                 return LastIndexOf(source, target, startIndex, count, GetOrdinalCompareOptions(options));
437             }
438
439             // startIndex is the index into source where we start search backwards from. leftStartIndex is the index into source
440             // of the start of the string that is count characters away from startIndex.
441             int leftStartIndex = (startIndex - count + 1);
442
443             fixed (char* pSource = source)
444             {
445                 int lastIndex = Interop.Globalization.LastIndexOf(_sortHandle, target, target.Length, pSource + (startIndex - count + 1), count, options);
446
447                 return lastIndex != -1 ? lastIndex + leftStartIndex : -1;
448             }
449         }
450
451         private bool StartsWith(string source, string prefix, CompareOptions options)
452         {
453             Debug.Assert(!_invariantMode);
454
455             Debug.Assert(!string.IsNullOrEmpty(source));
456             Debug.Assert(!string.IsNullOrEmpty(prefix));
457             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
458
459             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && prefix.IsFastSort())
460             {
461                 return IsPrefix(source, prefix, GetOrdinalCompareOptions(options));
462             }
463
464             return Interop.Globalization.StartsWith(_sortHandle, prefix, prefix.Length, source, source.Length, options);
465         }
466
467         private unsafe bool StartsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
468         {
469             Debug.Assert(!_invariantMode);
470
471             Debug.Assert(!source.IsEmpty);
472             Debug.Assert(!prefix.IsEmpty);
473             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
474
475             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
476             {
477                 if (source.Length < prefix.Length)
478                 {
479                     return false;
480                 }
481
482                 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
483                 {
484                     return StartsWithOrdinalIgnoreCaseHelper(source, prefix, options);
485                 }
486                 else
487                 {
488                     return StartsWithOrdinalHelper(source, prefix, options);
489                 }
490             }
491             else
492             {
493                 fixed (char* pSource = &MemoryMarshal.GetReference(source))
494                 fixed (char* pPrefix = &MemoryMarshal.GetReference(prefix))
495                 {
496                     return Interop.Globalization.StartsWith(_sortHandle, pPrefix, prefix.Length, pSource, source.Length, options);
497                 }
498             }
499         }
500
501         private unsafe bool StartsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
502         {
503             Debug.Assert(!_invariantMode);
504
505             Debug.Assert(!source.IsEmpty);
506             Debug.Assert(!prefix.IsEmpty);
507             Debug.Assert(_isAsciiEqualityOrdinal);
508             Debug.Assert(source.Length >= prefix.Length);
509
510             int length = prefix.Length;
511
512             fixed (char* ap = &MemoryMarshal.GetReference(source))
513             fixed (char* bp = &MemoryMarshal.GetReference(prefix))
514             {
515                 char* a = ap;
516                 char* b = bp;
517
518                 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
519                 {
520                     int charA = *a;
521                     int charB = *b;
522
523                     if (charA == charB)
524                     {
525                         a++; b++;
526                         length--;
527                         continue;
528                     }
529
530                     // uppercase both chars - notice that we need just one compare per char
531                     if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
532                     if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
533                     
534                     if (charA != charB)
535                         return false;
536
537                     // Next char
538                     a++; b++;
539                     length--;
540                 }
541
542                 if (length == 0) return true;
543                 return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
544             }
545         }
546
547         private unsafe bool StartsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> prefix, CompareOptions options)
548         {
549             Debug.Assert(!_invariantMode);
550
551             Debug.Assert(!source.IsEmpty);
552             Debug.Assert(!prefix.IsEmpty);
553             Debug.Assert(_isAsciiEqualityOrdinal);
554             Debug.Assert(source.Length >= prefix.Length);
555
556             int length = prefix.Length;
557
558             fixed (char* ap = &MemoryMarshal.GetReference(source))
559             fixed (char* bp = &MemoryMarshal.GetReference(prefix))
560             {
561                 char* a = ap;
562                 char* b = bp;
563
564                 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
565                 {
566                     int charA = *a;
567                     int charB = *b;
568                     
569                     if (charA != charB)
570                         return false;
571
572                     // Next char
573                     a++; b++;
574                     length--;
575                 }
576
577                 if (length == 0) return true;
578                 return Interop.Globalization.StartsWith(_sortHandle, b, length, a, length, options);
579             }
580         }
581
582         private bool EndsWith(string source, string suffix, CompareOptions options)
583         {
584             Debug.Assert(!_invariantMode);
585
586             Debug.Assert(!string.IsNullOrEmpty(source));
587             Debug.Assert(!string.IsNullOrEmpty(suffix));
588             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
589
590             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options) && source.IsFastSort() && suffix.IsFastSort())
591             {
592                 return IsSuffix(source, suffix, GetOrdinalCompareOptions(options));
593             }
594
595             return Interop.Globalization.EndsWith(_sortHandle, suffix, suffix.Length, source, source.Length, options);
596         }
597
598         private unsafe bool EndsWith(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
599         {
600             Debug.Assert(!_invariantMode);
601
602             Debug.Assert(!source.IsEmpty);
603             Debug.Assert(!suffix.IsEmpty);
604             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
605             
606             if (_isAsciiEqualityOrdinal && CanUseAsciiOrdinalForOptions(options))
607             {
608                 if (source.Length < suffix.Length)
609                 {
610                     return false;
611                 }
612
613                 if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
614                 {
615                     return EndsWithOrdinalIgnoreCaseHelper(source, suffix, options);
616                 }
617                 else
618                 {
619                     return EndsWithOrdinalHelper(source, suffix, options);
620                 }
621             }
622             else
623             {
624                 fixed (char* pSource = &MemoryMarshal.GetReference(source))
625                 fixed (char* pSuffix = &MemoryMarshal.GetReference(suffix))
626                 {
627                     return Interop.Globalization.EndsWith(_sortHandle, pSuffix, suffix.Length, pSource, source.Length, options);
628                 }
629             }
630         }
631
632         private unsafe bool EndsWithOrdinalIgnoreCaseHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
633         {
634             Debug.Assert(!_invariantMode);
635
636             Debug.Assert(!source.IsEmpty);
637             Debug.Assert(!suffix.IsEmpty);
638             Debug.Assert(_isAsciiEqualityOrdinal);
639             Debug.Assert(source.Length >= suffix.Length);
640
641             int length = suffix.Length;
642
643             fixed (char* ap = &MemoryMarshal.GetReference(source))
644             fixed (char* bp = &MemoryMarshal.GetReference(suffix))
645             {
646                 char* a = ap + source.Length - 1;
647                 char* b = bp + suffix.Length - 1;
648
649                 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
650                 {
651                     int charA = *a;
652                     int charB = *b;
653
654                     if (charA == charB)
655                     {
656                         a--; b--;
657                         length--;
658                         continue;
659                     }
660
661                     // uppercase both chars - notice that we need just one compare per char
662                     if ((uint)(charA - 'a') <= (uint)('z' - 'a')) charA -= 0x20;
663                     if ((uint)(charB - 'a') <= (uint)('z' - 'a')) charB -= 0x20;
664                     
665                     if (charA != charB)
666                         return false;
667
668                     // Next char
669                     a--; b--;
670                     length--;
671                 }
672
673                 if (length == 0) return true;
674                 return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
675             }
676         }
677
678         private unsafe bool EndsWithOrdinalHelper(ReadOnlySpan<char> source, ReadOnlySpan<char> suffix, CompareOptions options)
679         {
680             Debug.Assert(!_invariantMode);
681
682             Debug.Assert(!source.IsEmpty);
683             Debug.Assert(!suffix.IsEmpty);
684             Debug.Assert(_isAsciiEqualityOrdinal);
685             Debug.Assert(source.Length >= suffix.Length);
686
687             int length = suffix.Length;
688
689             fixed (char* ap = &MemoryMarshal.GetReference(source))
690             fixed (char* bp = &MemoryMarshal.GetReference(suffix))
691             {
692                 char* a = ap + source.Length - 1;
693                 char* b = bp + suffix.Length - 1;
694
695                 while (length != 0 && (*a < 0x80) && (*b < 0x80) && (!s_highCharTable[*a]) && (!s_highCharTable[*b]))
696                 {
697                     int charA = *a;
698                     int charB = *b;
699                     
700                     if (charA != charB)
701                         return false;
702
703                     // Next char
704                     a--; b--;
705                     length--;
706                 }
707
708                 if (length == 0) return true;
709                 return Interop.Globalization.EndsWith(_sortHandle, b - length + 1, length, a - length + 1, length, options);
710             }
711         }
712
713         private unsafe SortKey CreateSortKey(String source, CompareOptions options)
714         {
715             Debug.Assert(!_invariantMode);
716
717             if (source==null) { throw new ArgumentNullException(nameof(source)); }
718
719             if ((options & ValidSortkeyCtorMaskOffFlags) != 0)
720             {
721                 throw new ArgumentException(SR.Argument_InvalidFlag, nameof(options));
722             }
723             
724             byte [] keyData;
725             if (source.Length == 0)
726             { 
727                 keyData = Array.Empty<Byte>();
728             }
729             else
730             {
731                 int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
732                 keyData = new byte[sortKeyLength];
733
734                 fixed (byte* pSortKey = keyData)
735                 {
736                     if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
737                     {
738                         throw new ArgumentException(SR.Arg_ExternalException);
739                     }
740                 }
741             }
742
743             return new SortKey(Name, source, options, keyData);
744         }       
745
746         private static unsafe bool IsSortable(char *text, int length)
747         {
748             Debug.Assert(!GlobalizationMode.Invariant);
749
750             int index = 0;
751             UnicodeCategory uc;
752
753             while (index < length)
754             {
755                 if (Char.IsHighSurrogate(text[index]))
756                 {
757                     if (index == length - 1 || !Char.IsLowSurrogate(text[index+1]))
758                         return false; // unpaired surrogate
759
760                     uc = CharUnicodeInfo.GetUnicodeCategory(Char.ConvertToUtf32(text[index], text[index+1]));
761                     if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
762                         return false;
763
764                     index += 2;
765                     continue;
766                 }
767
768                 if (Char.IsLowSurrogate(text[index]))
769                 {
770                     return false; // unpaired surrogate
771                 }
772
773                 uc = CharUnicodeInfo.GetUnicodeCategory(text[index]);
774                 if (uc == UnicodeCategory.PrivateUse || uc == UnicodeCategory.OtherNotAssigned)
775                 {
776                     return false;
777                 }
778
779                 index++;
780             }
781
782             return true;
783         }
784
785         // -----------------------------
786         // ---- PAL layer ends here ----
787         // -----------------------------
788
789         internal unsafe int GetHashCodeOfStringCore(string source, CompareOptions options)
790         {
791             Debug.Assert(!_invariantMode);
792
793             Debug.Assert(source != null);
794             Debug.Assert((options & (CompareOptions.Ordinal | CompareOptions.OrdinalIgnoreCase)) == 0);
795
796             if (source.Length == 0)
797             {
798                 return 0;
799             }
800
801             int sortKeyLength = Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, null, 0, options);
802
803             byte[] borrowedArr = null;
804             Span<byte> span = sortKeyLength <= 512 ?
805                 stackalloc byte[512] :
806                 (borrowedArr = ArrayPool<byte>.Shared.Rent(sortKeyLength));
807
808             fixed (byte* pSortKey = &MemoryMarshal.GetReference(span))
809             {
810                 if (Interop.Globalization.GetSortKey(_sortHandle, source, source.Length, pSortKey, sortKeyLength, options) != sortKeyLength)
811                 {
812                     throw new ArgumentException(SR.Arg_ExternalException);
813                 }
814             }
815
816             int hash = Marvin.ComputeHash32(span.Slice(0, sortKeyLength), Marvin.DefaultSeed);
817
818             // Return the borrowed array if necessary.
819             if (borrowedArr != null)
820             {
821                 ArrayPool<byte>.Shared.Return(borrowedArr);
822             }
823
824             return hash;
825         }
826
827         private static CompareOptions GetOrdinalCompareOptions(CompareOptions options)
828         {
829             if ((options & CompareOptions.IgnoreCase) == CompareOptions.IgnoreCase)
830             {
831                 return CompareOptions.OrdinalIgnoreCase;
832             }
833             else
834             {
835                 return CompareOptions.Ordinal;
836             }
837         }
838
839         private static bool CanUseAsciiOrdinalForOptions(CompareOptions options)
840         {
841             // Unlike the other Ignore options, IgnoreSymbols impacts ASCII characters (e.g. ').
842             return (options & CompareOptions.IgnoreSymbols) == 0;
843         }
844
845         private static byte[] GetNullTerminatedUtf8String(string s)
846         {
847             int byteLen = System.Text.Encoding.UTF8.GetByteCount(s);
848
849             // Allocate an extra byte (which defaults to 0) as the null terminator.
850             byte[] buffer = new byte[byteLen + 1];
851
852             int bytesWritten = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, buffer, 0);
853
854             Debug.Assert(bytesWritten == byteLen);
855
856             return buffer;
857         }
858         
859         private SortVersion GetSortVersion()
860         {
861             Debug.Assert(!_invariantMode);
862
863             int sortVersion = Interop.Globalization.GetSortVersion(_sortHandle);
864             return new SortVersion(sortVersion, LCID, new Guid(sortVersion, 0, 0, 0, 0, 0, 0,
865                                                              (byte) (LCID >> 24),
866                                                              (byte) ((LCID  & 0x00FF0000) >> 16),
867                                                              (byte) ((LCID  & 0x0000FF00) >> 8),
868                                                              (byte) (LCID  & 0xFF)));
869         }
870
871         // See https://github.com/dotnet/coreclr/blob/master/src/utilcode/util_nodependencies.cpp#L970
872         private static readonly bool[] s_highCharTable = new bool[0x80]
873         {
874             true, /* 0x0, 0x0 */
875             true, /* 0x1, .*/
876             true, /* 0x2, .*/
877             true, /* 0x3, .*/
878             true, /* 0x4, .*/
879             true, /* 0x5, .*/
880             true, /* 0x6, .*/
881             true, /* 0x7, .*/
882             true, /* 0x8, .*/
883             false, /* 0x9,   */
884             true, /* 0xA,  */
885             false, /* 0xB, .*/
886             false, /* 0xC, .*/
887             true, /* 0xD,  */
888             true, /* 0xE, .*/
889             true, /* 0xF, .*/
890             true, /* 0x10, .*/
891             true, /* 0x11, .*/
892             true, /* 0x12, .*/
893             true, /* 0x13, .*/
894             true, /* 0x14, .*/
895             true, /* 0x15, .*/
896             true, /* 0x16, .*/
897             true, /* 0x17, .*/
898             true, /* 0x18, .*/
899             true, /* 0x19, .*/
900             true, /* 0x1A, */
901             true, /* 0x1B, .*/
902             true, /* 0x1C, .*/
903             true, /* 0x1D, .*/
904             true, /* 0x1E, .*/
905             true, /* 0x1F, .*/
906             false, /*0x20,  */
907             false, /*0x21, !*/
908             false, /*0x22, "*/
909             false, /*0x23,  #*/
910             false, /*0x24,  $*/
911             false, /*0x25,  %*/
912             false, /*0x26,  &*/
913             true,  /*0x27, '*/
914             false, /*0x28, (*/
915             false, /*0x29, )*/
916             false, /*0x2A **/
917             false, /*0x2B, +*/
918             false, /*0x2C, ,*/
919             true,  /*0x2D, -*/
920             false, /*0x2E, .*/
921             false, /*0x2F, /*/
922             false, /*0x30, 0*/
923             false, /*0x31, 1*/
924             false, /*0x32, 2*/
925             false, /*0x33, 3*/
926             false, /*0x34, 4*/
927             false, /*0x35, 5*/
928             false, /*0x36, 6*/
929             false, /*0x37, 7*/
930             false, /*0x38, 8*/
931             false, /*0x39, 9*/
932             false, /*0x3A, :*/
933             false, /*0x3B, ;*/
934             false, /*0x3C, <*/
935             false, /*0x3D, =*/
936             false, /*0x3E, >*/
937             false, /*0x3F, ?*/
938             false, /*0x40, @*/
939             false, /*0x41, A*/
940             false, /*0x42, B*/
941             false, /*0x43, C*/
942             false, /*0x44, D*/
943             false, /*0x45, E*/
944             false, /*0x46, F*/
945             false, /*0x47, G*/
946             false, /*0x48, H*/
947             false, /*0x49, I*/
948             false, /*0x4A, J*/
949             false, /*0x4B, K*/
950             false, /*0x4C, L*/
951             false, /*0x4D, M*/
952             false, /*0x4E, N*/
953             false, /*0x4F, O*/
954             false, /*0x50, P*/
955             false, /*0x51, Q*/
956             false, /*0x52, R*/
957             false, /*0x53, S*/
958             false, /*0x54, T*/
959             false, /*0x55, U*/
960             false, /*0x56, V*/
961             false, /*0x57, W*/
962             false, /*0x58, X*/
963             false, /*0x59, Y*/
964             false, /*0x5A, Z*/
965             false, /*0x5B, [*/
966             false, /*0x5C, \*/
967             false, /*0x5D, ]*/
968             false, /*0x5E, ^*/
969             false, /*0x5F, _*/
970             false, /*0x60, `*/
971             false, /*0x61, a*/
972             false, /*0x62, b*/
973             false, /*0x63, c*/
974             false, /*0x64, d*/
975             false, /*0x65, e*/
976             false, /*0x66, f*/
977             false, /*0x67, g*/
978             false, /*0x68, h*/
979             false, /*0x69, i*/
980             false, /*0x6A, j*/
981             false, /*0x6B, k*/
982             false, /*0x6C, l*/
983             false, /*0x6D, m*/
984             false, /*0x6E, n*/
985             false, /*0x6F, o*/
986             false, /*0x70, p*/
987             false, /*0x71, q*/
988             false, /*0x72, r*/
989             false, /*0x73, s*/
990             false, /*0x74, t*/
991             false, /*0x75, u*/
992             false, /*0x76, v*/
993             false, /*0x77, w*/
994             false, /*0x78, x*/
995             false, /*0x79, y*/
996             false, /*0x7A, z*/
997             false, /*0x7B, {*/
998             false, /*0x7C, |*/
999             false, /*0x7D, }*/
1000             false, /*0x7E, ~*/
1001             true, /*0x7F, \7f*/
1002         };
1003     }
1004 }