diff options
author | Stephen Toub <stoub@microsoft.com> | 2018-04-04 13:54:30 +0300 |
---|---|---|
committer | Jan Kotas <jkotas@microsoft.com> | 2018-04-05 18:25:06 +0300 |
commit | 4596d662c7598f67ccbb808561aa1e35a013ddb2 (patch) | |
tree | 1b3b38cb13275248e9f09f0e3efb74ea261a9ecc /src | |
parent | 1c8cdc1acff22077fd536ca691208e139ea98de0 (diff) |
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 <dotnet-bot@microsoft.com>
Diffstat (limited to 'src')
3 files changed, 236 insertions, 213 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs index d13d3e8ce..c431e462b 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Unix.cs @@ -13,116 +13,7 @@ namespace System.Globalization { private Tristate _needsTurkishCasing = Tristate.NotInitialized; - private void FinishInitialization() - { - } - - private unsafe string ChangeCase(string s, bool toUpper) - { - Debug.Assert(!_invariantMode); - - Debug.Assert(s != null); - - if (s.Length == 0) - { - return string.Empty; - } - - string result = string.FastAllocateString(s.Length); - - fixed (char* pSource = s) - { - fixed (char* pResult = result) - { -#if CORECLR - if (IsAsciiCasingSameAsInvariant && s.IsAscii()) - { - int length = s.Length; - char* a = pSource, b = pResult; - if (toUpper) - { - while (length-- != 0) - { - *b++ = ToUpperAsciiInvariant(*a++); - } - } - else - { - while (length-- != 0) - { - *b++ = ToLowerAsciiInvariant(*a++); - } - } - } - else -#endif - { - ChangeCase(pSource, s.Length, pResult, result.Length, toUpper); - } - } - } - - return result; - } - - internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper) - { - Debug.Assert(!_invariantMode); - Debug.Assert(destination.Length >= source.Length); - - if (source.IsEmpty) - { - return; - } - - fixed (char* pSource = &MemoryMarshal.GetReference(source)) - { - fixed (char* pResult = &MemoryMarshal.GetReference(destination)) - { - if (IsAsciiCasingSameAsInvariant) - { - int length = 0; - char* a = pSource, b = pResult; - if (toUpper) - { - while (length < source.Length && *a < 0x80) - { - *b++ = ToUpperAsciiInvariant(*a++); - length++; - } - } - else - { - while (length < source.Length && *a < 0x80) - { - *b++ = ToLowerAsciiInvariant(*a++); - length++; - } - } - - if (length != source.Length) - { - ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper); - } - } - else - { - ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper); - } - } - } - } - - private unsafe char ChangeCase(char c, bool toUpper) - { - Debug.Assert(!_invariantMode); - - char dst = default(char); - - ChangeCase(&c, 1, &dst, 1, toUpper); - - return dst; - } + private void FinishInitialization() { } // ----------------------------- // ---- PAL layer ends here ---- diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs index 015b37fcc..6e5e32100 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.Windows.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.Runtime.InteropServices; namespace System.Globalization { @@ -24,115 +23,33 @@ namespace System.Globalization _sortHandle = ret > 0 ? handle : IntPtr.Zero; } - private unsafe string ChangeCase(string s, bool toUpper) + private unsafe void ChangeCase(char* pSource, int pSourceLen, char* pResult, int pResultLen, bool toUpper) { Debug.Assert(!_invariantMode); - - Debug.Assert(s != null); - - // - // Get the length of the string. - // - int nLengthInput = s.Length; - - // - // Check if we have the empty string. - // - if (nLengthInput == 0) - { - return s; - } - - int ret; + Debug.Assert(pSource != null); + Debug.Assert(pResult != null); + Debug.Assert(pSourceLen >= 0); + Debug.Assert(pResultLen >= 0); + Debug.Assert(pSourceLen <= pResultLen); // Check for Invariant to avoid A/V in LCMapStringEx uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING; - // - // Create the result string. - // - string result = string.FastAllocateString(nLengthInput); - - fixed (char* pSource = s) - fixed (char* pResult = result) - { - ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName, - linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE), - pSource, - nLengthInput, - pResult, - nLengthInput, - null, - null, - _sortHandle); - } - + int ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName, + linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE), + pSource, + pSourceLen, + pResult, + pSourceLen, + null, + null, + _sortHandle); if (ret == 0) { throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); } - Debug.Assert(ret == nLengthInput, "Expected getting the same length of the original string"); - return result; - } - - internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper) - { - Debug.Assert(!_invariantMode); - Debug.Assert(destination.Length >= source.Length); - - if (source.IsEmpty) - { - return; - } - - int ret; - - // Check for Invariant to avoid A/V in LCMapStringEx - uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING; - - fixed (char* pSource = &MemoryMarshal.GetReference(source)) - fixed (char* pResult = &MemoryMarshal.GetReference(destination)) - { - ret = Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName, - linguisticCasing | (toUpper ? LCMAP_UPPERCASE : LCMAP_LOWERCASE), - pSource, - source.Length, - pResult, - source.Length, - null, - null, - _sortHandle); - } - - if (ret == 0) - { - throw new InvalidOperationException(SR.InvalidOperation_ReadOnly); - } - - Debug.Assert(ret == source.Length, "Expected getting the same length of the original span"); - } - - private unsafe char ChangeCase(char c, bool toUpper) - { - Debug.Assert(!_invariantMode); - - char retVal = '\0'; - - // Check for Invariant to avoid A/V in LCMapStringEx - uint linguisticCasing = IsInvariantLocale(_textInfoName) ? 0 : LCMAP_LINGUISTIC_CASING; - - Interop.Kernel32.LCMapStringEx(_sortHandle != IntPtr.Zero ? null : _textInfoName, - toUpper ? LCMAP_UPPERCASE | linguisticCasing : LCMAP_LOWERCASE | linguisticCasing, - &c, - 1, - &retVal, - 1, - null, - null, - _sortHandle); - - return retVal; + Debug.Assert(ret == pSourceLen, "Expected getting the same length of the original string"); } // PAL Ends here @@ -143,9 +60,6 @@ namespace System.Globalization private const uint LCMAP_LOWERCASE = 0x00000100; private const uint LCMAP_UPPERCASE = 0x00000200; - private static bool IsInvariantLocale(string localeName) - { - return localeName == ""; - } + private static bool IsInvariantLocale(string localeName) => localeName == ""; } } diff --git a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs index 431458bc7..e875e179f 100644 --- a/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs +++ b/src/System.Private.CoreLib/shared/System/Globalization/TextInfo.cs @@ -13,6 +13,7 @@ //////////////////////////////////////////////////////////////////////////// using System.Diagnostics; +using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; @@ -201,6 +202,223 @@ namespace System.Globalization return ChangeCase(str, toUpper: false); } + private unsafe char ChangeCase(char c, bool toUpper) + { + Debug.Assert(!_invariantMode); + + char dst = default; + ChangeCase(&c, 1, &dst, 1, toUpper); + return dst; + } + + private unsafe string ChangeCase(string source, bool toUpper) + { + Debug.Assert(!_invariantMode); + Debug.Assert(source != null); + + // If the string is empty, we're done. + if (source.Length == 0) + { + return string.Empty; + } + + int sourcePos = 0; + string result = null; + + // If this culture's casing for ASCII is the same as invariant, try to take + // a fast path that'll work in managed code and ASCII rather than calling out + // to the OS for culture-aware casing. + if (IsAsciiCasingSameAsInvariant) + { + if (toUpper) + { + // Loop through each character. + for (sourcePos = 0; sourcePos < source.Length; sourcePos++) + { + // If the character is lower-case, we're going to need to allocate a string. + char c = source[sourcePos]; + if ((uint)(c - 'a') <= 'z' - 'a') + { + // Allocate the result string. + result = string.FastAllocateString(source.Length); + fixed (char* pResult = result) + { + // Store all of characters examined thus far. + if (sourcePos > 0) + { + source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos)); + } + + // And store the current character, upper-cased. + char* d = pResult + sourcePos; + *d++ = (char)(c & ~0x20); + sourcePos++; + + // Then continue looping through the remainder of the characters. If we hit + // a non-ASCII character, bail to fall back to culture-aware casing. + for (; sourcePos < source.Length; sourcePos++) + { + c = source[sourcePos]; + if ((uint)(c - 'a') <= 'z' - 'a') + { + *d++ = (char)(c & ~0x20); + } + else if (!IsAscii(c)) + { + break; + } + else + { + *d++ = c; + } + } + } + + break; + } + else if (!IsAscii(c)) + { + // The character isn't ASCII; bail to fall back to a culture-aware casing. + break; + } + } + } + else // toUpper == false + { + // Loop through each character. + for (sourcePos = 0; sourcePos < source.Length; sourcePos++) + { + // If the character is upper-case, we're going to need to allocate a string. + char c = source[sourcePos]; + if ((uint)(c - 'A') <= 'Z' - 'A') + { + // Allocate the result string. + result = string.FastAllocateString(source.Length); + fixed (char* pResult = result) + { + // Store all of characters examined thus far. + if (sourcePos > 0) + { + source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos)); + } + + // And store the current character, upper-cased. + char* d = pResult + sourcePos; + *d++ = (char)(c | 0x20); + sourcePos++; + + // Then continue looping through the remainder of the characters. If we hit + // a non-ASCII character, bail to fall back to culture-aware casing. + for (; sourcePos < source.Length; sourcePos++) + { + c = source[sourcePos]; + if ((uint)(c - 'A') <= 'Z' - 'A') + { + *d++ = (char)(c | 0x20); + } + else if (!IsAscii(c)) + { + break; + } + else + { + *d++ = c; + } + } + } + + break; + } + else if (!IsAscii(c)) + { + // The character isn't ASCII; bail to fall back to a culture-aware casing. + break; + } + } + } + + // If we successfully iterated through all of the characters, we didn't need to fall back + // to culture-aware casing. In that case, if we allocated a result string, use it, otherwise + // just return the original string, as no modifications were necessary. + if (sourcePos == source.Length) + { + return result ?? source; + } + } + + // Falling back to culture-aware casing. Make sure we have a result string to write into. + // If we need to allocate the result string, we'll also need to copy over to it any + // characters already examined. + if (result == null) + { + result = string.FastAllocateString(source.Length); + if (sourcePos > 0) + { + fixed (char* pResult = result) + { + source.AsSpan(0, sourcePos).CopyTo(new Span<char>(pResult, sourcePos)); + } + } + } + + // Do the casing operation on everything after what we already processed. + fixed (char* pSource = source) + { + fixed (char* pResult = result) + { + ChangeCase(pSource + sourcePos, source.Length - sourcePos, pResult + sourcePos, result.Length - sourcePos, toUpper); + } + } + + return result; + } + + internal unsafe void ChangeCase(ReadOnlySpan<char> source, Span<char> destination, bool toUpper) + { + Debug.Assert(!_invariantMode); + Debug.Assert(destination.Length >= source.Length); + + if (source.IsEmpty) + { + return; + } + + fixed (char* pSource = &MemoryMarshal.GetReference(source)) + fixed (char* pResult = &MemoryMarshal.GetReference(destination)) + { + if (IsAsciiCasingSameAsInvariant) + { + int length = 0; + char* a = pSource, b = pResult; + if (toUpper) + { + while (length < source.Length && *a < 0x80) + { + *b++ = ToUpperAsciiInvariant(*a++); + length++; + } + } + else + { + while (length < source.Length && *a < 0x80) + { + *b++ = ToLowerAsciiInvariant(*a++); + length++; + } + } + + if (length != source.Length) + { + ChangeCase(a, source.Length - length, b, destination.Length - length, toUpper); + } + } + else + { + ChangeCase(pSource, source.Length, pResult, destination.Length, toUpper); + } + } + } + private unsafe string ToLowerAsciiInvariant(string s) { if (s.Length == 0) |