Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 33835aa

Browse files
stephentoubAnipik
authored andcommitted
Consolidate and optimize TextInfo.ChangeCase (dotnet/coreclr#17391)
- Move most of the implementation to the platform-agnostic file, rather than having completely different implementations for Windows and Unix. Now the only logic in each platform-specific file is the logic around invoking the associated P/Invoke. - Optimize that implementation to take a fast path that doesn't allocate when no case change is needed, and to avoid the native call when the whole string is ASCII. Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
1 parent ec067c8 commit 33835aa

File tree

3 files changed

+236
-213
lines changed

3 files changed

+236
-213
lines changed

src/Common/src/CoreLib/System/Globalization/TextInfo.Unix.cs

Lines changed: 1 addition & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -13,116 +13,7 @@ public partial class TextInfo
1313
{
1414
private Tristate _needsTurkishCasing = Tristate.NotInitialized;
1515

16-
private void FinishInitialization()
17-
{
18-
}
19-
20-
private unsafe string ChangeCase(string s, bool toUpper)
21-
{
22-
Debug.Assert(!_invariantMode);
23-
24-
Debug.Assert(s != null);
25-
26-
if (s.Length == 0)
27-
{
28-
return string.Empty;
29-
}
30-
31-
string result = string.FastAllocateString(s.Length);
32-
33-
fixed (char* pSource = s)
34-
{
35-
fixed (char* pResult = result)
36-
{
37-
#if CORECLR
38-
if (IsAsciiCasingSameAsInvariant && s.IsAscii())
39-
{
40-
int length = s.Length;
41-
char* a = pSource, b = pResult;
42-
if (toUpper)
43-
{
44-
while (length-- != 0)
45-
{
46-
*b++ = ToUpperAsciiInvariant(*a++);
47-
}
48-
}
49-
else
50-
{
51-
while (length-- != 0)
52-
{
53-
*b++ = ToLowerAsciiInvariant(*a++);
54-
}
55-
}
56-
}
57-
else
58-
#endif
59-
{
60-
ChangeCase(pSource, s.Length, pResult, result.Length, toUpper);
61-
}
62-
}
63-
}
64-
65-
return result;
66-
}
67-
68-
internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
69-
{
70-
Debug.Assert(!_invariantMode);
71-
Debug.Assert(destination.Length >= source.Length);
72-
73-
if (source.IsEmpty)
74-
{
75-
return;
76-
}
77-
78-
fixed (char* pSource = &MemoryMarshal.GetReference(source))
79-
{
80-
fixed (char* pResult = &MemoryMarshal.GetReference(destination))
81-
{
82-
if (IsAsciiCasingSameAsInvariant)
83-
{
84-
int length = 0;
85-
char* a = pSource, b = pResult;
86-
if (toUpper)
87-
{
88-
while (length < source.Length && *a < 0x80)
89-
{
90-
*b++ = ToUpperAsciiInvariant(*a++);
91-
length++;
92-
}
93-
}
94-
else
95-
{
96-
while (length < source.Length && *a < 0x80)
97-
{
98-
*b++ = ToLowerAsciiInvariant(*a++);
99-
length++;
100-
}
101-
}
102-
103-
if (length != source.Length)
104-
{
105-
ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper);
106-
}
107-
}
108-
else
109-
{
110-
ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper);
111-
}
112-
}
113-
}
114-
}
115-
116-
private unsafe char ChangeCase(char c, bool toUpper)
117-
{
118-
Debug.Assert(!_invariantMode);
119-
120-
char dst = default(char);
121-
122-
ChangeCase(&c, 1, &dst, 1, toUpper);
123-
124-
return dst;
125-
}
16+
private void FinishInitialization() { }
12617

12718
// -----------------------------
12819
// ---- PAL layer ends here ----

src/Common/src/CoreLib/System/Globalization/TextInfo.Windows.cs

Lines changed: 17 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Diagnostics;
6-
using System.Runtime.InteropServices;
76

87
namespace System.Globalization
98
{
@@ -24,115 +23,33 @@ private unsafe void FinishInitialization()
2423
_sortHandle = ret > 0 ? handle : IntPtr.Zero;
2524
}
2625

27-
private unsafe string ChangeCase(string s, bool toUpper)
26+
private unsafe void ChangeCase(char* pSource, int pSourceLen, char* pResult, int pResultLen, bool toUpper)
2827
{
2928
Debug.Assert(!_invariantMode);
30-
31-
Debug.Assert(s != null);
32-
33-
//
34-
// Get the length of the string.
35-
//
36-
int nLengthInput = s.Length;
37-
38-
//
39-
// Check if we have the empty string.
40-
//
41-
if (nLengthInput == 0)
42-
{
43-
return s;
44-
}
45-
46-
int ret;
29+
Debug.Assert(pSource != null);
30+
Debug.Assert(pResult != null);
31+
Debug.Assert(pSourceLen >= 0);
32+
Debug.Assert(pResultLen >= 0);
33+
Debug.Assert(pSourceLen <= pResultLen);
4734

4835
// Check for Invariant to avoid A/V in LCMapStringEx
4936
uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
5037

51-
//
52-
// Create the result string.
53-
//
54-
string result = string.FastAllocateString(nLengthInput);
55-
56-
fixed (char* pSource = s)
57-
fixed (char* pResult = result)
58-
{
59-
ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
60-
linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
61-
pSource,
62-
nLengthInput,
63-
pResult,
64-
nLengthInput,
65-
null,
66-
null,
67-
_sortHandle);
68-
}
69-
38+
int ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
39+
linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
40+
pSource,
41+
pSourceLen,
42+
pResult,
43+
pSourceLen,
44+
null,
45+
null,
46+
_sortHandle);
7047
if (ret == 0)
7148
{
7249
throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
7350
}
7451

75-
Debug.Assert(ret == nLengthInput, "Expected getting the same length of the original string");
76-
return result;
77-
}
78-
79-
internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper)
80-
{
81-
Debug.Assert(!_invariantMode);
82-
Debug.Assert(destination.Length >= source.Length);
83-
84-
if (source.IsEmpty)
85-
{
86-
return;
87-
}
88-
89-
int ret;
90-
91-
// Check for Invariant to avoid A/V in LCMapStringEx
92-
uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
93-
94-
fixed (char* pSource = &MemoryMarshal.GetReference(source))
95-
fixed (char* pResult = &MemoryMarshal.GetReference(destination))
96-
{
97-
ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
98-
linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE),
99-
pSource,
100-
source.Length,
101-
pResult,
102-
source.Length,
103-
null,
104-
null,
105-
_sortHandle);
106-
}
107-
108-
if (ret == 0)
109-
{
110-
throw new InvalidOperationException(SR.InvalidOperation_ReadOnly);
111-
}
112-
113-
Debug.Assert(ret == source.Length, "Expected getting the same length of the original span");
114-
}
115-
116-
private unsafe char ChangeCase(char c, bool toUpper)
117-
{
118-
Debug.Assert(!_invariantMode);
119-
120-
char retVal = '\0';
121-
122-
// Check for Invariant to avoid A/V in LCMapStringEx
123-
uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING;
124-
125-
Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName,
126-
toUpper ? LCMAP_UPPERCASE | linguisticCasing : LCMAP_LOWERCASE | linguisticCasing,
127-
&c,
128-
1,
129-
&retVal,
130-
1,
131-
null,
132-
null,
133-
_sortHandle);
134-
135-
return retVal;
52+
Debug.Assert(ret == pSourceLen, "Expected getting the same length of the original string");
13653
}
13754

13855
// PAL Ends here
@@ -143,9 +60,6 @@ private unsafe char ChangeCase(char c, bool toUpper)
14360
private const uint LCMAP_LOWERCASE = 0x00000100;
14461
private const uint LCMAP_UPPERCASE = 0x00000200;
14562

146-
private static bool IsInvariantLocale(string localeName)
147-
{
148-
return localeName == "";
149-
}
63+
private static bool IsInvariantLocale(string localeName) => localeName == "";
15064
}
15165
}

0 commit comments

Comments
 (0)