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