Skip to content

Commit

Permalink
Use ReadOnlySpan properties over array fields (#56845)
Browse files Browse the repository at this point in the history
  • Loading branch information
NewellClark authored Jan 11, 2022
1 parent 12be03b commit f29f937
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 16 deletions.
6 changes: 3 additions & 3 deletions src/Compilers/CSharp/Portable/Parser/QuickScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,14 @@ private SyntaxToken QuickScanSyntaxToken()

//localize frequently accessed fields
var charWindow = TextWindow.CharacterWindow;
var charPropLength = s_charProperties.Length;
var charPropLength = CharProperties.Length;

for (; i < n; i++)
{
char c = charWindow[i];
int uc = unchecked((int)c);

var flags = uc < charPropLength ? (CharFlags)s_charProperties[uc] : CharFlags.Complex;
var flags = uc < charPropLength ? (CharFlags)CharProperties[uc] : CharFlags.Complex;

state = (QuickScanState)s_stateTransitions[(int)state, (int)flags];
// NOTE: that Bad > Done and it is the only state like that
Expand Down Expand Up @@ -273,7 +273,7 @@ private SyntaxToken CreateQuickToken()
// # is marked complex as it may start directives.
// PERF: Use byte instead of CharFlags so the compiler can use array literal initialization.
// The most natural type choice, Enum arrays, are not blittable due to a CLR limitation.
private static readonly byte[] s_charProperties = new[]
private static ReadOnlySpan<byte> CharProperties => new[]
{
// 0 .. 31
(byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex, (byte)CharFlags.Complex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Reflection.Emit;

Expand All @@ -23,7 +24,7 @@ internal static OperandType ReadOperandType(ImmutableArray<byte> il, ref int pos
}

// internal for testing
internal static readonly byte[] OneByte = new byte[]
internal static ReadOnlySpan<byte> OneByte => new byte[]
{
(byte)OperandType.InlineNone, // nop
(byte)OperandType.InlineNone, // break
Expand Down Expand Up @@ -283,7 +284,7 @@ internal static OperandType ReadOperandType(ImmutableArray<byte> il, ref int pos
};

// internal for testing
internal static readonly byte[] TwoByte = new byte[]
internal static ReadOnlySpan<byte> TwoByte => new byte[]
{
(byte)OperandType.InlineNone, // arglist (0xfe 0x00)
(byte)OperandType.InlineNone, // ceq
Expand Down
22 changes: 11 additions & 11 deletions src/Compilers/Core/Portable/Xml/XmlCharType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ internal static class XmlCharType
0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0x00, 0x00,
};

private static byte charProperties(char i)
private static byte GetCharProperties(char i)
{
// The index entry, table, identifies the start of the appropriate 256-entry table within s_charProperties
byte table = s_charPropertiesIndex[i >> innerSizeBits];
Expand Down Expand Up @@ -910,7 +910,7 @@ private static byte charProperties(char i)

public static bool IsWhiteSpace(char ch)
{
return (charProperties(ch) & fWhitespace) != 0;
return (GetCharProperties(ch) & fWhitespace) != 0;
}

public static bool IsExtender(char ch)
Expand All @@ -920,7 +920,7 @@ public static bool IsExtender(char ch)

public static bool IsNCNameSingleChar(char ch)
{
return (charProperties(ch) & fNCNameSC) != 0;
return (GetCharProperties(ch) & fNCNameSC) != 0;
}

#if XML10_FIFTH_EDITION
Expand Down Expand Up @@ -955,7 +955,7 @@ public static bool IsNCNameLowSurrogateChar(char lowChar)

public static bool IsStartNCNameSingleChar(char ch)
{
return (charProperties(ch) & fNCStartNameSC) != 0;
return (GetCharProperties(ch) & fNCStartNameSC) != 0;
}

#if XML10_FIFTH_EDITION
Expand All @@ -982,7 +982,7 @@ public static bool IsStartNameSingleChar(char ch)

public static bool IsCharData(char ch)
{
return (charProperties(ch) & fCharData) != 0;
return (GetCharProperties(ch) & fCharData) != 0;
}

// [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | [-'()+,./:=?;!*#@$_%] Section 2.3 of spec
Expand All @@ -998,26 +998,26 @@ public static bool IsPubidChar(char ch)
// TextChar = CharData - { 0xA, 0xD, '<', '&', ']' }
internal static bool IsTextChar(char ch)
{
return (charProperties(ch) & fText) != 0;
return (GetCharProperties(ch) & fText) != 0;
}

// AttrValueChar = CharData - { 0xA, 0xD, 0x9, '<', '>', '&', '\'', '"' }
internal static bool IsAttributeValueChar(char ch)
{
return (charProperties(ch) & fAttrValue) != 0;
return (GetCharProperties(ch) & fAttrValue) != 0;
}

// XML 1.0 Fourth Edition definitions
//
public static bool IsLetter(char ch)
{
return (charProperties(ch) & fLetter) != 0;
return (GetCharProperties(ch) & fLetter) != 0;
}

// This method uses the XML 4th edition name character ranges
public static bool IsNCNameCharXml4e(char ch)
{
return (charProperties(ch) & fNCNameXml4e) != 0;
return (GetCharProperties(ch) & fNCNameXml4e) != 0;
}

// This method uses the XML 4th edition name character ranges
Expand Down Expand Up @@ -1089,7 +1089,7 @@ internal static int IsOnlyWhitespaceWithPos(string str)
{
for (int i = 0; i < str.Length; i++)
{
if ((charProperties(str[i]) & fWhitespace) == 0)
if ((GetCharProperties(str[i]) & fWhitespace) == 0)
{
return i;
}
Expand All @@ -1104,7 +1104,7 @@ internal static int IsOnlyCharData(string str)
{
for (int i = 0; i < str.Length; i++)
{
if ((charProperties(str[i]) & fCharData) == 0)
if ((GetCharProperties(str[i]) & fCharData) == 0)
{
if (i + 1 >= str.Length || !(XmlCharType.IsHighSurrogate(str[i]) && XmlCharType.IsLowSurrogate(str[i + 1])))
{
Expand Down
112 changes: 112 additions & 0 deletions src/Compilers/Test/Core/Assert/AssertEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,22 @@ public static void Equal<T>(
Assert.True(false, GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine));
}

public static void Equal<T>(
ReadOnlySpan<T> expected,
ReadOnlySpan<T> actual,
IEqualityComparer<T> comparer = null,
string message = null,
string itemSeparator = null,
Func<T, string> itemInspector = null,
string expectedValueSourcePath = null,
int expectedValueSourceLine = 0)
{
if (SequenceEqual(expected, actual, comparer))
return;

Assert.True(false, GetAssertMessage(expected, actual, comparer, message, itemInspector, itemSeparator, expectedValueSourcePath, expectedValueSourceLine));
}

/// <summary>
/// Asserts that two strings are equal, and prints a diff between the two if they are not.
/// </summary>
Expand Down Expand Up @@ -336,6 +352,22 @@ private static bool SequenceEqual<T>(IEnumerable<T> expected, IEnumerable<T> act
return true;
}

private static bool SequenceEqual<T>(ReadOnlySpan<T> expected, ReadOnlySpan<T> actual, IEqualityComparer<T> comparer = null)
{
if (expected.Length != actual.Length)
return false;

for (int i = 0; i < expected.Length; i++)
{
if (!(comparer is not null ? comparer.Equals(expected[i], actual[i]) : AssertEqualityComparer<T>.Equals(expected[i], actual[i])))
{
return false;
}
}

return true;
}

public static void SetEqual(IEnumerable<string> expected, IEnumerable<string> actual, IEqualityComparer<string> comparer = null, string message = null, string itemSeparator = "\r\n", Func<string, string> itemInspector = null)
{
var indexes = new Dictionary<string, int>(comparer);
Expand Down Expand Up @@ -673,6 +705,86 @@ public static string GetAssertMessage<T>(
return message.ToString();
}

public static string GetAssertMessage<T>(
ReadOnlySpan<T> expected,
ReadOnlySpan<T> actual,
IEqualityComparer<T> comparer = null,
string prefix = null,
Func<T, string> itemInspector = null,
string itemSeparator = null,
string expectedValueSourcePath = null,
int expectedValueSourceLine = 0)
{
if (itemInspector == null)
{
if (typeof(T) == typeof(byte))
{
itemInspector = b => $"0x{b:X2}";
}
else
{
itemInspector = new Func<T, string>(obj => (obj != null) ? obj.ToString() : "<null>");
}
}

if (itemSeparator == null)
{
if (typeof(T) == typeof(byte))
{
itemSeparator = ", ";
}
else
{
itemSeparator = "," + Environment.NewLine;
}
}

const int maxDisplayedExpectedEntries = 10;
var expectedString = join(itemSeparator, expected[..Math.Min(expected.Length, maxDisplayedExpectedEntries)], itemInspector);
var actualString = join(itemSeparator, actual, itemInspector);

var message = new StringBuilder();

if (!string.IsNullOrEmpty(prefix))
{
message.AppendLine(prefix);
message.AppendLine();
}

message.AppendLine("Expected:");
message.AppendLine(expectedString);
if (expected.Length > maxDisplayedExpectedEntries)
{
message.AppendLine("... truncated ...");
}

message.AppendLine("Actual:");
message.AppendLine(actualString);
message.AppendLine("Differences:");
message.AppendLine(DiffUtil.DiffReport(expected.ToArray(), actual.ToArray(), itemSeparator, comparer, itemInspector));

if (TryGenerateExpectedSourceFileAndGetDiffLink(actualString, expected.Length, expectedValueSourcePath, expectedValueSourceLine, out var link))
{
message.AppendLine(link);
}

return message.ToString();

static string join(string itemSeparator, ReadOnlySpan<T> items, Func<T, string> itemInspector)
{
var result = new StringBuilder();
var iter = items.GetEnumerator();

if (iter.MoveNext())
result.Append(itemInspector(iter.Current));

while (iter.MoveNext())
result.Append($"{itemSeparator}{itemInspector(iter.Current)}");

return result.ToString();
}
}

internal static bool TryGenerateExpectedSourceFileAndGetDiffLink(string actualString, int expectedLineCount, string expectedValueSourcePath, int expectedValueSourceLine, out string link)
{
// add a link to a .cmd file that opens a diff tool:
Expand Down

0 comments on commit f29f937

Please sign in to comment.