Skip to content

Commit

Permalink
Consolidate and optimize TextInfo.ChangeCase (dotnet#17391)
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
stephentoub authored Apr 4, 2018
1 parent fff9f71 commit d1f49cc
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 213 deletions.
111 changes: 1 addition & 110 deletions src/mscorlib/shared/System/Globalization/TextInfo.Unix.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,116 +13,7 @@ public partial class TextInfo
{
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 ----
Expand Down
120 changes: 17 additions & 103 deletions src/mscorlib/shared/System/Globalization/TextInfo.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -24,115 +23,33 @@ private unsafe void FinishInitialization()
_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
Expand All @@ -143,9 +60,6 @@ private unsafe char ChangeCase(char c, bool toUpper)
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 == "";
}
}
Loading

0 comments on commit d1f49cc

Please sign in to comment.