// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.Arm;
-using System.Runtime.Intrinsics.X86;
-
namespace System.Buffers.Text
{
public static partial class Utf8Formatter
{
- #region Constants
-
- private const byte OpenBrace = (byte)'{';
- private const byte CloseBrace = (byte)'}';
-
- private const byte OpenParen = (byte)'(';
- private const byte CloseParen = (byte)')';
-
- private const byte Dash = (byte)'-';
-
- #endregion Constants
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static (Vector128<byte>, Vector128<byte>, Vector128<byte>) FormatGuidVector128Utf8(Guid value, bool useDashes)
- {
- Debug.Assert((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian);
- // Vectorized implementation for D, N, P and B formats:
- // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
-
- Vector128<byte> hexMap = Vector128.Create(
- (byte)'0', (byte)'1', (byte)'2', (byte)'3',
- (byte)'4', (byte)'5', (byte)'6', (byte)'7',
- (byte)'8', (byte)'9', (byte)'a', (byte)'b',
- (byte)'c', (byte)'d', (byte)'e', (byte)'f');
-
- Vector128<byte> srcVec = Unsafe.As<Guid, Vector128<byte>>(ref value);
- (Vector128<byte> hexLow, Vector128<byte> hexHigh) =
- HexConverter.AsciiToHexVector128(srcVec, hexMap);
-
- // because of Guid's layout (int _a, short _b, _c, <8 byte fields>)
- // we have to shuffle some bytes for _a, _b and _c
- hexLow = Vector128.Shuffle(hexLow.AsInt16(), Vector128.Create(3, 2, 1, 0, 5, 4, 7, 6)).AsByte();
-
- if (useDashes)
- {
- // We divide 16 bytes into 3 x Vector128<byte>:
- //
- // ________-____-____-____-____________
- // xxxxxxxxxxxxxxxx
- // yyyyyyyyyyyyyyyy
- // zzzzzzzzzzzzzzzz
- //
- // Vector "x" - just one dash, shift all elements after it.
- Vector128<byte> vecX = Vector128.Shuffle(hexLow,
- Vector128.Create(0x706050403020100, 0xD0CFF0B0A0908FF).AsByte());
-
- // Vector "y" - same here.
- Vector128<byte> vecY = Vector128.Shuffle(hexHigh,
- Vector128.Create(0x7060504FF030201, 0xF0E0D0C0B0A0908).AsByte());
-
- // Vector "z" - we need to merge some elements of hexLow with hexHigh and add 4 dashes.
- Vector128<byte> mid1 = Vector128.Shuffle(hexLow,
- Vector128.Create(0x0D0CFF0B0A0908FF, 0xFFFFFFFFFFFF0F0E).AsByte());
- Vector128<byte> mid2 = Vector128.Shuffle(hexHigh,
- Vector128.Create(0xFFFFFFFFFFFFFFFF, 0xFF03020100FFFFFF).AsByte());
- Vector128<byte> dashesMask = Vector128.Shuffle(Vector128.CreateScalarUnsafe((byte)'-'),
- Vector128.Create(0xFFFF00FFFFFFFF00, 0x00FFFFFFFF00FFFF).AsByte());
-
- Vector128<byte> vecZ = (mid1 | mid2 | dashesMask);
- return (vecX, vecY, vecZ);
- }
-
- // N format - no dashes.
- return (hexLow, hexHigh, default);
- }
-
/// <summary>
/// Formats a Guid as a UTF8 string.
/// </summary>
/// </exceptions>
public static bool TryFormat(Guid value, Span<byte> destination, out int bytesWritten, StandardFormat format = default)
{
- const int INSERT_DASHES = unchecked((int)0x80000000);
- const int NO_DASHES = 0;
- const int INSERT_CURLY_BRACES = (CloseBrace << 16) | (OpenBrace << 8);
- const int INSERT_ROUND_BRACES = (CloseParen << 16) | (OpenParen << 8);
- const int NO_BRACES = 0;
- const int LEN_GUID_BASE = 32;
- const int LEN_ADD_DASHES = 4;
- const int LEN_ADD_BRACES = 2;
-
- // This is a 32-bit value whose contents (where 0 is the low byte) are:
- // 0th byte: minimum required length of the output buffer,
- // 1st byte: the ASCII byte to insert for the opening brace position (or 0 if no braces),
- // 2nd byte: the ASCII byte to insert for the closing brace position (or 0 if no braces),
- // 3rd byte: high bit set if dashes are to be inserted.
- //
- // The reason for keeping a single flag instead of separate vars is that we can avoid register spillage
- // as we build up the output value.
int flags;
switch (FormattingHelpers.GetSymbolOrDefault(format, 'D'))
{
case 'D': // nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn
- flags = INSERT_DASHES + NO_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES;
+ flags = 36 + Guid.TryFormatFlags_UseDashes;
break;
case 'B': // {nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn}
- flags = INSERT_DASHES + INSERT_CURLY_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
+ flags = 38 + Guid.TryFormatFlags_UseDashes + Guid.TryFormatFlags_CurlyBraces;
break;
case 'P': // (nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn)
- flags = INSERT_DASHES + INSERT_ROUND_BRACES + LEN_GUID_BASE + LEN_ADD_DASHES + LEN_ADD_BRACES;
+ flags = 38 + Guid.TryFormatFlags_UseDashes + Guid.TryFormatFlags_Parens;
break;
case 'N': // nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
- flags = NO_BRACES + NO_DASHES + LEN_GUID_BASE;
+ flags = 32;
break;
default:
goto case 'D'; // unreachable
}
- // At this point, the low byte of flags contains the minimum required length
-
- if ((byte)flags > destination.Length)
- {
- bytesWritten = 0;
- return false;
- }
-
- bytesWritten = (byte)flags;
- flags >>= 8;
-
- // At this point, the low byte of flags contains the opening brace char (if any)
-
- if ((byte)flags != 0)
- {
- destination[0] = (byte)flags;
- destination = destination.Slice(1);
- }
- flags >>= 8;
-
- if ((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian)
- {
- // Vectorized implementation for D, N, P and B formats:
- // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
- (Vector128<byte> vecX, Vector128<byte> vecY, Vector128<byte> vecZ) =
- FormatGuidVector128Utf8(value, flags < 0 /*dash*/);
-
- ref byte dest = ref MemoryMarshal.GetReference(destination);
- if (flags < 0)
- {
- // We need to merge these vectors in this order:
- // xxxxxxxxxxxxxxxx
- // yyyyyyyyyyyyyyyy
- // zzzzzzzzzzzzzzzz
- vecX.StoreUnsafe(ref dest, 0);
- vecY.StoreUnsafe(ref dest, 20);
- vecZ.StoreUnsafe(ref dest, 8);
- }
- else
- {
- // xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyy
- vecX.StoreUnsafe(ref dest, 0);
- vecY.StoreUnsafe(ref dest, 16);
- }
- if ((byte)flags != 0)
- Unsafe.Add(ref dest, flags < 0 ? 36 : 32) = (byte)flags;
- return true;
- }
-
- // At this point, the low byte of flags contains the closing brace char (if any)
- // And since we're performing arithmetic shifting the high bit of flags is set (flags is negative) if dashes are required
-
- DecomposedGuid guidAsBytes = default;
- guidAsBytes.Guid = value;
-
- // When a GUID is blitted, the first three components are native-endian, and the last component is big-endian.
-
- // The line below forces the JIT to hoist the bounds check for the following segment.
- // The JIT will optimize away the read, but it cannot optimize away the bounds check
- // because it may have an observable side effect (throwing).
- // We use 8 instead of 7 so that we also capture the dash if we're asked to insert one.
-
- { _ = destination[8]; }
- if (BitConverter.IsLittleEndian)
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte03, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte02, destination, 2, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte01, destination, 4, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte00, destination, 6, HexConverter.Casing.Lower);
- }
- else
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte00, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte01, destination, 2, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte02, destination, 4, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte03, destination, 6, HexConverter.Casing.Lower);
- }
-
- if (flags < 0 /* use dash? */)
- {
- destination[8] = Dash;
- destination = destination.Slice(9);
- }
- else
- {
- destination = destination.Slice(8);
- }
-
- { _ = destination[4]; }
- if (BitConverter.IsLittleEndian)
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte05, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte04, destination, 2, HexConverter.Casing.Lower);
- }
- else
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte04, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte05, destination, 2, HexConverter.Casing.Lower);
- }
-
- if (flags < 0 /* use dash? */)
- {
- destination[4] = Dash;
- destination = destination.Slice(5);
- }
- else
- {
- destination = destination.Slice(4);
- }
-
- { _ = destination[4]; }
- if (BitConverter.IsLittleEndian)
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte07, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte06, destination, 2, HexConverter.Casing.Lower);
- }
- else
- {
- HexConverter.ToBytesBuffer(guidAsBytes.Byte06, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte07, destination, 2, HexConverter.Casing.Lower);
- }
-
- if (flags < 0 /* use dash? */)
- {
- destination[4] = Dash;
- destination = destination.Slice(5);
- }
- else
- {
- destination = destination.Slice(4);
- }
-
- { _ = destination[4]; }
- HexConverter.ToBytesBuffer(guidAsBytes.Byte08, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte09, destination, 2, HexConverter.Casing.Lower);
-
- if (flags < 0 /* use dash? */)
- {
- destination[4] = Dash;
- destination = destination.Slice(5);
- }
- else
- {
- destination = destination.Slice(4);
- }
-
- { _ = destination[11]; } // can't hoist bounds check on the final brace (if exists)
- HexConverter.ToBytesBuffer(guidAsBytes.Byte10, destination, 0, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte11, destination, 2, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte12, destination, 4, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte13, destination, 6, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte14, destination, 8, HexConverter.Casing.Lower);
- HexConverter.ToBytesBuffer(guidAsBytes.Byte15, destination, 10, HexConverter.Casing.Lower);
-
- if ((byte)flags != 0)
- {
- destination[12] = (byte)flags;
- }
-
- return true;
- }
-
- /// <summary>
- /// Used to provide access to the individual bytes of a GUID.
- /// </summary>
- [StructLayout(LayoutKind.Explicit)]
- private struct DecomposedGuid
- {
- [FieldOffset(00)] public Guid Guid;
- [FieldOffset(00)] public byte Byte00;
- [FieldOffset(01)] public byte Byte01;
- [FieldOffset(02)] public byte Byte02;
- [FieldOffset(03)] public byte Byte03;
- [FieldOffset(04)] public byte Byte04;
- [FieldOffset(05)] public byte Byte05;
- [FieldOffset(06)] public byte Byte06;
- [FieldOffset(07)] public byte Byte07;
- [FieldOffset(08)] public byte Byte08;
- [FieldOffset(09)] public byte Byte09;
- [FieldOffset(10)] public byte Byte10;
- [FieldOffset(11)] public byte Byte11;
- [FieldOffset(12)] public byte Byte12;
- [FieldOffset(13)] public byte Byte13;
- [FieldOffset(14)] public byte Byte14;
- [FieldOffset(15)] public byte Byte15;
+ return value.TryFormatCore(destination, out bytesWritten, flags);
}
}
}
IComparable,
IComparable<Guid>,
IEquatable<Guid>,
- ISpanParsable<Guid>
+ ISpanParsable<Guid>,
+ IUtf8SpanFormattable
{
public static readonly Guid Empty;
if (format.Length != 1)
{
// all acceptable format strings are of length 1
- throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ ThrowBadGuidFormatSpecification();
}
input = input.Trim();
return true;
}
- // Returns the guid in "registry" format.
- public override string ToString() => ToString("D", null);
-
public override int GetHashCode()
{
// Simply XOR all the bits of the GUID 32 bits at a time.
public static bool operator !=(Guid a, Guid b) => !EqualsCore(a, b);
- public string ToString([StringSyntax(StringSyntaxAttribute.GuidFormat)] string? format)
- {
- return ToString(format, null);
- }
-
- private static unsafe int HexsToChars(char* guidChars, int a, int b)
+ private static unsafe int HexsToChars<TChar>(TChar* guidChars, int a, int b) where TChar : unmanaged, IUtfChar<TChar>
{
- guidChars[0] = HexConverter.ToCharLower(a >> 4);
- guidChars[1] = HexConverter.ToCharLower(a);
+ guidChars[0] = TChar.CastFrom(HexConverter.ToCharLower(a >> 4));
+ guidChars[1] = TChar.CastFrom(HexConverter.ToCharLower(a));
- guidChars[2] = HexConverter.ToCharLower(b >> 4);
- guidChars[3] = HexConverter.ToCharLower(b);
+ guidChars[2] = TChar.CastFrom(HexConverter.ToCharLower(b >> 4));
+ guidChars[3] = TChar.CastFrom(HexConverter.ToCharLower(b));
return 4;
}
- private static unsafe int HexsToCharsHexOutput(char* guidChars, int a, int b)
+ private static unsafe int HexsToCharsHexOutput<TChar>(TChar* guidChars, int a, int b) where TChar : unmanaged, IUtfChar<TChar>
{
- guidChars[0] = '0';
- guidChars[1] = 'x';
+ guidChars[0] = TChar.CastFrom('0');
+ guidChars[1] = TChar.CastFrom('x');
- guidChars[2] = HexConverter.ToCharLower(a >> 4);
- guidChars[3] = HexConverter.ToCharLower(a);
+ guidChars[2] = TChar.CastFrom(HexConverter.ToCharLower(a >> 4));
+ guidChars[3] = TChar.CastFrom(HexConverter.ToCharLower(a));
- guidChars[4] = ',';
- guidChars[5] = '0';
- guidChars[6] = 'x';
+ guidChars[4] = TChar.CastFrom(',');
+ guidChars[5] = TChar.CastFrom('0');
+ guidChars[6] = TChar.CastFrom('x');
- guidChars[7] = HexConverter.ToCharLower(b >> 4);
- guidChars[8] = HexConverter.ToCharLower(b);
+ guidChars[7] = TChar.CastFrom(HexConverter.ToCharLower(b >> 4));
+ guidChars[8] = TChar.CastFrom(HexConverter.ToCharLower(b));
return 9;
}
+ // Returns the guid in "registry" format.
+ public override string ToString() => ToString("d", null);
+
+ public string ToString([StringSyntax(StringSyntaxAttribute.GuidFormat)] string? format)
+ {
+ return ToString(format, null);
+ }
+
// IFormattable interface
// We currently ignore provider
public string ToString([StringSyntax(StringSyntaxAttribute.GuidFormat)] string? format, IFormatProvider? provider)
{
+ int guidSize;
if (string.IsNullOrEmpty(format))
{
- format = "D";
+ guidSize = 36;
}
-
- // all acceptable format strings are of length 1
- if (format.Length != 1)
+ else
{
- throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
- }
+ // all acceptable format strings are of length 1
+ if (format.Length != 1)
+ {
+ ThrowBadGuidFormatSpecification();
+ }
- int guidSize;
- switch (format[0])
- {
- case 'D':
- case 'd':
- guidSize = 36;
- break;
- case 'N':
- case 'n':
- guidSize = 32;
- break;
- case 'B':
- case 'b':
- case 'P':
- case 'p':
- guidSize = 38;
- break;
- case 'X':
- case 'x':
- guidSize = 68;
- break;
- default:
- throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ switch (format[0] | 0x20)
+ {
+ case 'd':
+ guidSize = 36;
+ break;
+
+ case 'n':
+ guidSize = 32;
+ break;
+
+ case 'b' or 'p':
+ guidSize = 38;
+ break;
+
+ case 'x':
+ guidSize = 68;
+ break;
+
+ default:
+ guidSize = 0;
+ ThrowBadGuidFormatSpecification();
+ break;
+ };
}
string guidString = string.FastAllocateString(guidSize);
- bool result = TryFormat(new Span<char>(ref guidString.GetRawStringData(), guidString.Length), out int bytesWritten, format);
+ bool result = TryFormatCore(new Span<char>(ref guidString.GetRawStringData(), guidString.Length), out int bytesWritten, format);
Debug.Assert(result && bytesWritten == guidString.Length, "Formatting guid should have succeeded.");
return guidString;
}
- // Returns whether the guid is successfully formatted as a span.
- public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format = default)
+ public bool TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format = default) =>
+ TryFormatCore(destination, out charsWritten, format);
+
+ bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format, IFormatProvider? provider) =>
+ // Provider is ignored.
+ TryFormatCore(destination, out charsWritten, format);
+
+ public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format = default) =>
+ TryFormatCore(utf8Destination, out bytesWritten, format);
+
+ bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format, IFormatProvider? provider) =>
+ // Provider is ignored.
+ TryFormatCore(utf8Destination, out bytesWritten, format);
+
+ // TryFormatCore accepts an `int flags` composed of:
+ // - Lowest byte: required length
+ // - Second byte: opening brace char, or 0 if no braces
+ // - Third byte: closing brace char, or 0 if no braces
+ // - Highest bit: 1 if use dashes, else 0
+ internal const int TryFormatFlags_UseDashes = unchecked((int)0x80000000);
+ internal const int TryFormatFlags_CurlyBraces = ('}' << 16) | ('{' << 8);
+ internal const int TryFormatFlags_Parens = (')' << 16) | ('(' << 8);
+
+ private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten, ReadOnlySpan<char> format) where TChar : unmanaged, IUtfChar<TChar>
{
+ int flags;
+
if (format.Length == 0)
{
- format = "D";
+ flags = 36 + TryFormatFlags_UseDashes;
}
- // all acceptable format strings are of length 1
- if (format.Length != 1)
+ else
{
- throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
- }
+ if (format.Length != 1)
+ {
+ ThrowBadGuidFormatSpecification();
+ }
- bool dash = true;
- bool hex = false;
- int braces = 0;
+ switch (format[0] | 0x20)
+ {
+ case 'd':
+ flags = 36 + TryFormatFlags_UseDashes;
+ break;
- int guidSize;
+ case 'p':
+ flags = 38 + TryFormatFlags_UseDashes + TryFormatFlags_Parens;
+ break;
- switch (format[0])
- {
- case 'D':
- case 'd':
- guidSize = 36;
- break;
- case 'N':
- case 'n':
- dash = false;
- guidSize = 32;
- break;
- case 'B':
- case 'b':
- braces = '{' + ('}' << 16);
- guidSize = 38;
- break;
- case 'P':
- case 'p':
- braces = '(' + (')' << 16);
- guidSize = 38;
- break;
- case 'X':
- case 'x':
- braces = '{' + ('}' << 16);
- dash = false;
- hex = true;
- guidSize = 68;
- break;
- default:
- throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
+ case 'b':
+ flags = 38 + TryFormatFlags_UseDashes + TryFormatFlags_CurlyBraces;
+ break;
+
+ case 'n':
+ flags = 32;
+ break;
+
+ case 'x':
+ return TryFormatX(destination, out charsWritten);
+
+ default:
+ flags = 0;
+ ThrowBadGuidFormatSpecification();
+ break;
+ }
}
- if (destination.Length < guidSize)
+ return TryFormatCore(destination, out charsWritten, flags);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)] // only used from two callers
+ internal unsafe bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten, int flags) where TChar : unmanaged, IUtfChar<TChar>
+ {
+ // The low byte of flags contains the required length.
+ if ((byte)flags > destination.Length)
{
charsWritten = 0;
return false;
}
- unsafe
+ charsWritten = (byte)flags;
+ flags >>= 8;
+
+ fixed (TChar* guidChars = &MemoryMarshal.GetReference(destination))
{
- fixed (char* guidChars = &MemoryMarshal.GetReference(destination))
+ TChar* p = guidChars;
+
+ // The low byte of flags now contains the opening brace char (if any)
+ if ((byte)flags != 0)
{
- char* p = guidChars;
+ *p++ = TChar.CastFrom((byte)flags);
+ }
+ flags >>= 8;
- if (braces != 0)
- *p++ = (char)braces;
+ if ((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian)
+ {
+ // Vectorized implementation for D, N, P and B formats:
+ // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
+ (Vector128<byte> vecX, Vector128<byte> vecY, Vector128<byte> vecZ) = FormatGuidVector128Utf8(this, flags < 0 /* dash */);
- if (hex)
+ if (typeof(TChar) == typeof(byte))
{
- // {0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}
- *p++ = '0';
- *p++ = 'x';
- p += HexsToChars(p, _a >> 24, _a >> 16);
- p += HexsToChars(p, _a >> 8, _a);
- *p++ = ',';
- *p++ = '0';
- *p++ = 'x';
- p += HexsToChars(p, _b >> 8, _b);
- *p++ = ',';
- *p++ = '0';
- *p++ = 'x';
- p += HexsToChars(p, _c >> 8, _c);
- *p++ = ',';
- *p++ = '{';
- p += HexsToCharsHexOutput(p, _d, _e);
- *p++ = ',';
- p += HexsToCharsHexOutput(p, _f, _g);
- *p++ = ',';
- p += HexsToCharsHexOutput(p, _h, _i);
- *p++ = ',';
- p += HexsToCharsHexOutput(p, _j, _k);
- *p++ = '}';
+ byte* pChar = (byte*)p;
+ if (flags < 0 /* dash */)
+ {
+ // We need to merge these vectors in this order:
+ // xxxxxxxxxxxxxxxx
+ // yyyyyyyyyyyyyyyy
+ // zzzzzzzzzzzzzzzz
+ vecX.Store(pChar);
+ vecY.Store(pChar + 20);
+ vecZ.Store(pChar + 8);
+ p += 36;
+ }
+ else
+ {
+ // xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyy
+ vecX.Store(pChar);
+ vecY.Store(pChar + 16);
+ p += 32;
+ }
}
- else if ((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian)
+ else
{
- // Vectorized implementation for D, N, P and B formats:
- // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
- (Vector128<byte> vecX, Vector128<byte> vecY, Vector128<byte> vecZ) =
- Buffers.Text.Utf8Formatter.FormatGuidVector128Utf8(this, dash);
-
// Expand to UTF-16
(Vector128<ushort> x0, Vector128<ushort> x1) = Vector128.Widen(vecX);
(Vector128<ushort> y0, Vector128<ushort> y1) = Vector128.Widen(vecY);
ushort* pChar = (ushort*)p;
- if (dash)
+ if (flags < 0 /* dash */)
{
(Vector128<ushort> z0, Vector128<ushort> z1) = Vector128.Widen(vecZ);
// xxxxxxxxxxxxxxxx
// yyyyyyyyyyyyyyyy
// zzzzzzzzzzzzzzzz
- x0.Store(pChar + 0);
+ x0.Store(pChar);
y0.Store(pChar + 20);
y1.Store(pChar + 28);
z0.Store(pChar + 8); // overlaps x1
else
{
// xxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyy
- x0.Store(pChar + 0);
+ x0.Store(pChar);
x1.Store(pChar + 8);
y0.Store(pChar + 16);
y1.Store(pChar + 24);
p += 32;
}
- if (braces != 0)
- *p = (char)(braces >> 16);
- charsWritten = guidSize;
- return true;
}
- else
+ }
+ else
+ {
+ // Non-vectorized fallback for D, N, P and B formats:
+ // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
+ p += HexsToChars(p, _a >> 24, _a >> 16);
+ p += HexsToChars(p, _a >> 8, _a);
+ if (flags < 0 /* dash */)
{
- // Non-vectorized fallback for D, N, P and B formats:
- // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
- p += HexsToChars(p, _a >> 24, _a >> 16);
- p += HexsToChars(p, _a >> 8, _a);
- if (dash)
- *p++ = '-';
- p += HexsToChars(p, _b >> 8, _b);
- if (dash)
- *p++ = '-';
- p += HexsToChars(p, _c >> 8, _c);
- if (dash)
- *p++ = '-';
- p += HexsToChars(p, _d, _e);
- if (dash)
- *p++ = '-';
- p += HexsToChars(p, _f, _g);
- p += HexsToChars(p, _h, _i);
- p += HexsToChars(p, _j, _k);
+ *p++ = TChar.CastFrom('-');
}
+ p += HexsToChars(p, _b >> 8, _b);
+ if (flags < 0 /* dash */)
+ {
+ *p++ = TChar.CastFrom('-');
+ }
+ p += HexsToChars(p, _c >> 8, _c);
+ if (flags < 0 /* dash */)
+ {
+ *p++ = TChar.CastFrom('-');
+ }
+ p += HexsToChars(p, _d, _e);
+ if (flags < 0 /* dash */)
+ {
+ *p++ = TChar.CastFrom('-');
+ }
+ p += HexsToChars(p, _f, _g);
+ p += HexsToChars(p, _h, _i);
+ p += HexsToChars(p, _j, _k);
+ }
- if (braces != 0)
- *p++ = (char)(braces >> 16);
-
- Debug.Assert(p - guidChars == guidSize);
+ // The low byte of flags now contains the closing brace char (if any)
+ if ((byte)flags != 0)
+ {
+ *p = TChar.CastFrom((byte)flags);
}
+
+ Debug.Assert(p == guidChars + charsWritten - ((byte)flags != 0 ? 1 : 0));
+ }
+
+ return true;
+ }
+
+ private unsafe bool TryFormatX<TChar>(Span<TChar> destination, out int charsWritten) where TChar : unmanaged, IUtfChar<TChar>
+ {
+ if (destination.Length < 68)
+ {
+ charsWritten = 0;
+ return false;
+ }
+ charsWritten = 68;
+
+ fixed (TChar* guidChars = &MemoryMarshal.GetReference(destination))
+ {
+ TChar* p = guidChars;
+
+ // {0xdddddddd,0xdddd,0xdddd,{0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd}}
+ *p++ = TChar.CastFrom('{');
+ *p++ = TChar.CastFrom('0');
+ *p++ = TChar.CastFrom('x');
+ p += HexsToChars(p, _a >> 24, _a >> 16);
+ p += HexsToChars(p, _a >> 8, _a);
+ *p++ = TChar.CastFrom(',');
+ *p++ = TChar.CastFrom('0');
+ *p++ = TChar.CastFrom('x');
+ p += HexsToChars(p, _b >> 8, _b);
+ *p++ = TChar.CastFrom(',');
+ *p++ = TChar.CastFrom('0');
+ *p++ = TChar.CastFrom('x');
+ p += HexsToChars(p, _c >> 8, _c);
+ *p++ = TChar.CastFrom(',');
+ *p++ = TChar.CastFrom('{');
+ p += HexsToCharsHexOutput(p, _d, _e);
+ *p++ = TChar.CastFrom(',');
+ p += HexsToCharsHexOutput(p, _f, _g);
+ *p++ = TChar.CastFrom(',');
+ p += HexsToCharsHexOutput(p, _h, _i);
+ *p++ = TChar.CastFrom(',');
+ p += HexsToCharsHexOutput(p, _j, _k);
+ *p++ = TChar.CastFrom('}');
+ *p = TChar.CastFrom('}');
+
+ Debug.Assert(p == guidChars + charsWritten - 1);
}
- charsWritten = guidSize;
return true;
}
- bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, [StringSyntax(StringSyntaxAttribute.GuidFormat)] ReadOnlySpan<char> format, IFormatProvider? provider)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static (Vector128<byte>, Vector128<byte>, Vector128<byte>) FormatGuidVector128Utf8(Guid value, bool useDashes)
{
- // Like with the IFormattable implementation, provider is ignored.
- return TryFormat(destination, out charsWritten, format);
+ Debug.Assert((Ssse3.IsSupported || AdvSimd.Arm64.IsSupported) && BitConverter.IsLittleEndian);
+ // Vectorized implementation for D, N, P and B formats:
+ // [{|(]dddddddd[-]dddd[-]dddd[-]dddd[-]dddddddddddd[}|)]
+
+ Vector128<byte> hexMap = Vector128.Create(
+ (byte)'0', (byte)'1', (byte)'2', (byte)'3',
+ (byte)'4', (byte)'5', (byte)'6', (byte)'7',
+ (byte)'8', (byte)'9', (byte)'a', (byte)'b',
+ (byte)'c', (byte)'d', (byte)'e', (byte)'f');
+
+ Vector128<byte> srcVec = Unsafe.As<Guid, Vector128<byte>>(ref value);
+ (Vector128<byte> hexLow, Vector128<byte> hexHigh) =
+ HexConverter.AsciiToHexVector128(srcVec, hexMap);
+
+ // because of Guid's layout (int _a, short _b, _c, <8 byte fields>)
+ // we have to shuffle some bytes for _a, _b and _c
+ hexLow = Vector128.Shuffle(hexLow.AsInt16(), Vector128.Create(3, 2, 1, 0, 5, 4, 7, 6)).AsByte();
+
+ if (useDashes)
+ {
+ // We divide 16 bytes into 3 x Vector128<byte>:
+ //
+ // ________-____-____-____-____________
+ // xxxxxxxxxxxxxxxx
+ // yyyyyyyyyyyyyyyy
+ // zzzzzzzzzzzzzzzz
+ //
+ // Vector "x" - just one dash, shift all elements after it.
+ Vector128<byte> vecX = Vector128.Shuffle(hexLow,
+ Vector128.Create(0x706050403020100, 0xD0CFF0B0A0908FF).AsByte());
+
+ // Vector "y" - same here.
+ Vector128<byte> vecY = Vector128.Shuffle(hexHigh,
+ Vector128.Create(0x7060504FF030201, 0xF0E0D0C0B0A0908).AsByte());
+
+ // Vector "z" - we need to merge some elements of hexLow with hexHigh and add 4 dashes.
+ Vector128<byte> mid1 = Vector128.Shuffle(hexLow,
+ Vector128.Create(0x0D0CFF0B0A0908FF, 0xFFFFFFFFFFFF0F0E).AsByte());
+ Vector128<byte> mid2 = Vector128.Shuffle(hexHigh,
+ Vector128.Create(0xFFFFFFFFFFFFFFFF, 0xFF03020100FFFFFF).AsByte());
+ Vector128<byte> dashesMask = Vector128.Shuffle(Vector128.CreateScalarUnsafe((byte)'-'),
+ Vector128.Create(0xFFFF00FFFFFFFF00, 0x00FFFFFFFF00FFFF).AsByte());
+
+ Vector128<byte> vecZ = (mid1 | mid2 | dashesMask);
+ return (vecX, vecY, vecZ);
+ }
+
+ // N format - no dashes.
+ return (hexLow, hexHigh, default);
}
//
/// <inheritdoc cref="ISpanParsable{TSelf}.TryParse(ReadOnlySpan{char}, IFormatProvider?, out TSelf)" />
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Guid result) => TryParse(s, out result);
+
+ [DoesNotReturn]
+ private static void ThrowBadGuidFormatSpecification() =>
+ throw new FormatException(SR.Format_InvalidGuidFormatSpecification);
}
}
Timeout = 3,
NotApplicable = 4,
}
- public readonly partial struct Guid : System.IComparable, System.IComparable<System.Guid>, System.IEquatable<System.Guid>, System.IFormattable, System.IParsable<System.Guid>, System.ISpanFormattable, System.ISpanParsable<System.Guid>
+ public readonly partial struct Guid : System.IComparable, System.IComparable<System.Guid>, System.IEquatable<System.Guid>, System.IFormattable, System.IParsable<System.Guid>, System.ISpanFormattable, System.ISpanParsable<System.Guid>, System.IUtf8SpanFormattable
{
private readonly int _dummyPrimitive;
public static readonly System.Guid Empty;
public static System.Guid ParseExact(System.ReadOnlySpan<char> input, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format) { throw null; }
public static System.Guid ParseExact(string input, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string format) { throw null; }
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
+ bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
public byte[] ToByteArray() { throw null; }
public override string ToString() { throw null; }
public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format) { throw null; }
public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] string? format, System.IFormatProvider? provider) { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format = default(System.ReadOnlySpan<char>)) { throw null; }
+ public bool TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("GuidFormat")] System.ReadOnlySpan<char> format = default(System.ReadOnlySpan<char>)) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> input, out System.Guid result) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider, out System.Guid result) { throw null; }
public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? input, out System.Guid result) { throw null; }
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Text;
using Xunit;
namespace System.Tests
{
Assert.Throws<FormatException>(() => s_testGuid.TryFormat(new Span<char>(), out int charsWritten, format));
Assert.Throws<FormatException>(() => s_testGuid.TryFormat(new Span<char>(), out int charsWritten, format.ToUpperInvariant()));
+
+ Assert.Throws<FormatException>(() => ((ISpanFormattable)s_testGuid).TryFormat(new Span<char>(), out int charsWritten, format, null));
+ Assert.Throws<FormatException>(() => ((ISpanFormattable)s_testGuid).TryFormat(new Span<char>(), out int charsWritten, format.ToUpperInvariant(), null));
+
+ Assert.Throws<FormatException>(() => s_testGuid.TryFormat(new Span<byte>(), out int bytesWritten, format));
+ Assert.Throws<FormatException>(() => s_testGuid.TryFormat(new Span<byte>(), out int bytesWritten, format.ToUpperInvariant()));
}
[Theory]
public static void TryFormat_LengthTooSmall_ReturnsFalse(Guid guid, string format, string expected)
{
_ = expected;
+
Assert.False(guid.TryFormat(new Span<char>(new char[guid.ToString(format).Length - 1]), out int charsWritten, format));
Assert.Equal(0, charsWritten);
+
+ Assert.False(((ISpanFormattable)guid).TryFormat(new Span<char>(new char[guid.ToString(format).Length - 1]), out charsWritten, format, null));
+ Assert.Equal(0, charsWritten);
+
+ Assert.False(guid.TryFormat(new Span<byte>(new byte[guid.ToString(format).Length - 1]), out int bytesWritten, format));
+ Assert.Equal(0, bytesWritten);
+
+ Assert.False(((IUtf8SpanFormattable)guid).TryFormat(new Span<byte>(new byte[guid.ToString(format).Length - 1]), out bytesWritten, format, null));
+ Assert.Equal(0, bytesWritten);
}
[Theory]
public static void TryFormat_CharsWritten_EqualsZero_WhenSpanTooSmall(Guid guid, string format, string expected)
{
_ = expected;
+
Assert.False(guid.TryFormat(new Span<char>(new char[guid.ToString(format).Length - 1]), out int charsWritten, format));
Assert.Equal(0, charsWritten);
+
+ Assert.False(((ISpanFormattable)guid).TryFormat(new Span<char>(new char[guid.ToString(format).Length - 1]), out charsWritten, format, null));
+ Assert.Equal(0, charsWritten);
+
+ Assert.False(guid.TryFormat(new Span<byte>(new byte[guid.ToString(format).Length - 1]), out int bytesWritten, format));
+ Assert.Equal(0, bytesWritten);
+
+ Assert.False(((IUtf8SpanFormattable)guid).TryFormat(new Span<byte>(new byte[guid.ToString(format).Length - 1]), out bytesWritten, format, null));
+ Assert.Equal(0, bytesWritten);
}
[Theory]
[MemberData(nameof(ToString_TestData))]
public static void TryFormat_ValidLength_ReturnsTrue(Guid guid, string format, string expected)
{
- char[] chars = new char[guid.ToString(format).Length];
- Assert.True(guid.TryFormat(new Span<char>(chars), out int charsWritten, format));
- Assert.Equal(chars, expected.ToCharArray());
+ for (int additionalSpace = 0; additionalSpace < 2; additionalSpace++)
+ {
+ char[] chars = new char[expected.Length + additionalSpace];
+ byte[] bytes = new byte[expected.Length + additionalSpace];
+
+ Assert.True(guid.TryFormat(new Span<char>(chars), out int charsWritten, format));
+ Assert.Equal(expected.Length, charsWritten);
+ Assert.Equal(expected, chars.AsSpan(0, charsWritten).ToString());
+
+ Assert.True(((ISpanFormattable)guid).TryFormat(new Span<char>(chars), out charsWritten, format, null));
+ Assert.Equal(expected.Length, charsWritten);
+ Assert.Equal(expected, chars.AsSpan(0, charsWritten).ToString());
+
+ Assert.True(guid.TryFormat(new Span<byte>(bytes), out int bytesWritten, format));
+ Assert.Equal(expected.Length, bytesWritten);
+ Assert.Equal(expected, Encoding.UTF8.GetString(bytes.AsSpan(0, bytesWritten)));
+
+ Assert.True(((IUtf8SpanFormattable)guid).TryFormat(new Span<byte>(bytes), out bytesWritten, format, null));
+ Assert.Equal(expected.Length, bytesWritten);
+ Assert.Equal(expected, Encoding.UTF8.GetString(bytes.AsSpan(0, bytesWritten)));
+ }
}
}
}