Skip to content

Commit 55feab8

Browse files
committed
Remove span pinning associated with string.Create
This is now possible with C# 11.
1 parent 62f3eb2 commit 55feab8

File tree

9 files changed

+157
-192
lines changed

9 files changed

+157
-192
lines changed

src/libraries/Common/src/System/HexConverter.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,14 +208,10 @@ public static unsafe string ToString(ReadOnlySpan<byte> bytes, Casing casing = C
208208
}
209209
return result.ToString();
210210
#else
211-
fixed (byte* bytesPtr = bytes)
212-
{
213-
return string.Create(bytes.Length * 2, (Ptr: (IntPtr)bytesPtr, bytes.Length, casing), static (chars, args) =>
214-
{
215-
var ros = new ReadOnlySpan<byte>((byte*)args.Ptr, args.Length);
216-
EncodeToUtf16(ros, chars, args.casing);
217-
});
218-
}
211+
#pragma warning disable CS8500 // takes address of managed type
212+
return string.Create(bytes.Length * 2, (RosPtr: (IntPtr)(&bytes), casing), static (chars, args) =>
213+
EncodeToUtf16(*(ReadOnlySpan<byte>*)args.RosPtr, chars, args.casing));
214+
#pragma warning restore CS8500
219215
#endif
220216
}
221217

src/libraries/Common/src/System/IO/Archiving.Utils.Windows.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,22 +61,21 @@ public static unsafe string EntryFromPath(ReadOnlySpan<char> path, bool appendPa
6161
string.Empty;
6262
}
6363

64-
fixed (char* pathPtr = &MemoryMarshal.GetReference(path))
64+
#pragma warning disable CS8500 // takes address of managed type
65+
return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, RosPtr: (IntPtr)(&path)), static (dest, state) =>
6566
{
66-
return string.Create(appendPathSeparator ? path.Length + 1 : path.Length, (appendPathSeparator, (IntPtr)pathPtr, path.Length), static (dest, state) =>
67+
var path = *(ReadOnlySpan<char>*)state.RosPtr;
68+
path.CopyTo(dest);
69+
if (state.appendPathSeparator)
6770
{
68-
ReadOnlySpan<char> path = new ReadOnlySpan<char>((char*)state.Item2, state.Length);
69-
path.CopyTo(dest);
70-
if (state.appendPathSeparator)
71-
{
72-
dest[^1] = '/';
73-
}
71+
dest[^1] = '/';
72+
}
7473

75-
// To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1,
76-
// all slashes should be forward slashes.
77-
dest.Replace('\\', '/');
78-
});
79-
}
74+
// To ensure tar files remain compatible with Unix, and per the ZIP File Format Specification 4.4.17.1,
75+
// all slashes should be forward slashes.
76+
dest.Replace('\\', '/');
77+
});
78+
#pragma warning restore CS8500
8079
}
8180
}
8281
}

src/libraries/System.Private.CoreLib/src/System/IO/Path.cs

Lines changed: 52 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -686,132 +686,78 @@ private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan
686686
{
687687
Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths");
688688

689-
bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
690-
|| PathInternal.IsDirectorySeparator(second[0]);
689+
bool hasSeparator = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]);
691690

692691
return hasSeparator ?
693692
string.Concat(first, second) :
694693
string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second);
695694
}
696695

697-
private readonly unsafe struct Join3Payload
698-
{
699-
public Join3Payload(char* first, int firstLength, char* second, int secondLength, char* third, int thirdLength, byte separators)
700-
{
701-
First = first;
702-
FirstLength = firstLength;
703-
Second = second;
704-
SecondLength = secondLength;
705-
Third = third;
706-
ThirdLength = thirdLength;
707-
Separators = separators;
708-
}
709-
710-
public readonly char* First;
711-
public readonly int FirstLength;
712-
public readonly char* Second;
713-
public readonly int SecondLength;
714-
public readonly char* Third;
715-
public readonly int ThirdLength;
716-
public readonly byte Separators;
717-
}
718-
719696
private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third)
720697
{
721698
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths");
722699

723-
byte firstNeedsSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
724-
|| PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1;
725-
byte secondNeedsSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
726-
|| PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1;
700+
bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]);
701+
bool secondHasSeparator = PathInternal.IsDirectorySeparator(second[^1]) || PathInternal.IsDirectorySeparator(third[0]);
727702

728-
fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third))
703+
return (firstHasSeparator, secondHasSeparator) switch
729704
{
730-
var payload = new Join3Payload(
731-
f, first.Length, s, second.Length, t, third.Length,
732-
(byte)(firstNeedsSeparator | secondNeedsSeparator << 1));
733-
734-
return string.Create(
735-
first.Length + second.Length + third.Length + firstNeedsSeparator + secondNeedsSeparator,
736-
(IntPtr)(&payload),
737-
static (destination, statePtr) =>
738-
{
739-
ref Join3Payload state = ref *(Join3Payload*)statePtr;
740-
new Span<char>(state.First, state.FirstLength).CopyTo(destination);
741-
if ((state.Separators & 0b1) != 0)
742-
destination[state.FirstLength] = PathInternal.DirectorySeparatorChar;
743-
new Span<char>(state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.Separators & 0b1)));
744-
if ((state.Separators & 0b10) != 0)
745-
destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar;
746-
new Span<char>(state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength));
747-
});
748-
}
749-
}
750-
751-
private readonly unsafe struct Join4Payload
752-
{
753-
public Join4Payload(char* first, int firstLength, char* second, int secondLength, char* third, int thirdLength, char* fourth, int fourthLength, byte separators)
754-
{
755-
First = first;
756-
FirstLength = firstLength;
757-
Second = second;
758-
SecondLength = secondLength;
759-
Third = third;
760-
ThirdLength = thirdLength;
761-
Fourth = fourth;
762-
FourthLength = fourthLength;
763-
Separators = separators;
764-
}
765-
766-
public readonly char* First;
767-
public readonly int FirstLength;
768-
public readonly char* Second;
769-
public readonly int SecondLength;
770-
public readonly char* Third;
771-
public readonly int ThirdLength;
772-
public readonly char* Fourth;
773-
public readonly int FourthLength;
774-
public readonly byte Separators;
705+
(false, false) => string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second, PathInternal.DirectorySeparatorCharAsString, third),
706+
(false, true) => string.Concat(first, PathInternal.DirectorySeparatorCharAsString, second, third),
707+
(true, false) => string.Concat(first, second, PathInternal.DirectorySeparatorCharAsString, third),
708+
(true, true) => string.Concat(first, second, third),
709+
};
775710
}
776711

777712
private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth)
778713
{
779714
Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths");
780715

781-
byte firstNeedsSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1])
782-
|| PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1;
783-
byte secondNeedsSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1])
784-
|| PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1;
785-
byte thirdNeedsSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1])
786-
|| PathInternal.IsDirectorySeparator(fourth[0]) ? (byte)0 : (byte)1;
716+
byte firstNeedsSeparator = PathInternal.IsDirectorySeparator(first[^1]) || PathInternal.IsDirectorySeparator(second[0]) ? (byte)0 : (byte)1;
717+
byte secondNeedsSeparator = PathInternal.IsDirectorySeparator(second[^1]) || PathInternal.IsDirectorySeparator(third[0]) ? (byte)0 : (byte)1;
718+
byte thirdNeedsSeparator = PathInternal.IsDirectorySeparator(third[^1]) || PathInternal.IsDirectorySeparator(fourth[0]) ? (byte)0 : (byte)1;
787719

788-
fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth))
789-
{
790-
var payload = new Join4Payload(
791-
f, first.Length, s, second.Length, t, third.Length, u, fourth.Length,
792-
(byte)(firstNeedsSeparator | secondNeedsSeparator << 1 | thirdNeedsSeparator << 2));
793-
794-
return string.Create(
795-
first.Length + second.Length + third.Length + fourth.Length + firstNeedsSeparator + secondNeedsSeparator + thirdNeedsSeparator,
796-
(IntPtr)(&payload),
797-
static (destination, statePtr) =>
720+
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
721+
return string.Create(
722+
first.Length + second.Length + third.Length + fourth.Length + firstNeedsSeparator + secondNeedsSeparator + thirdNeedsSeparator,
723+
(FirstRos: (IntPtr)(&first), SecondRos: (IntPtr)(&second), ThirdRos: (IntPtr)(&third), FourthRos: (IntPtr)(&fourth), firstNeedsSeparator, secondNeedsSeparator, thirdNeedsSeparator),
724+
static (destination, state) =>
725+
{
726+
ReadOnlySpan<char> first = *(ReadOnlySpan<char>*)state.FirstRos;
727+
first.CopyTo(destination);
728+
destination = destination.Slice(first.Length);
729+
730+
if (state.firstNeedsSeparator != 0)
798731
{
799-
ref Join4Payload state = ref *(Join4Payload*)statePtr;
800-
new Span<char>(state.First, state.FirstLength).CopyTo(destination);
801-
int insertionPoint = state.FirstLength;
802-
if ((state.Separators & 0b1) != 0)
803-
destination[insertionPoint++] = PathInternal.DirectorySeparatorChar;
804-
new Span<char>(state.Second, state.SecondLength).CopyTo(destination.Slice(insertionPoint));
805-
insertionPoint += state.SecondLength;
806-
if ((state.Separators & 0b10) != 0)
807-
destination[insertionPoint++] = PathInternal.DirectorySeparatorChar;
808-
new Span<char>(state.Third, state.ThirdLength).CopyTo(destination.Slice(insertionPoint));
809-
insertionPoint += state.ThirdLength;
810-
if ((state.Separators & 0b100) != 0)
811-
destination[insertionPoint++] = PathInternal.DirectorySeparatorChar;
812-
new Span<char>(state.Fourth, state.FourthLength).CopyTo(destination.Slice(insertionPoint));
813-
});
814-
}
732+
destination[0] = PathInternal.DirectorySeparatorChar;
733+
destination = destination.Slice(1);
734+
}
735+
736+
ReadOnlySpan<char> second = *(ReadOnlySpan<char>*)state.SecondRos;
737+
second.CopyTo(destination);
738+
destination = destination.Slice(second.Length);
739+
740+
if (state.secondNeedsSeparator != 0)
741+
{
742+
destination[0] = PathInternal.DirectorySeparatorChar;
743+
destination = destination.Slice(1);
744+
}
745+
746+
ReadOnlySpan<char> third = *(ReadOnlySpan<char>*)state.ThirdRos;
747+
third.CopyTo(destination);
748+
destination = destination.Slice(third.Length);
749+
750+
if (state.thirdNeedsSeparator != 0)
751+
{
752+
destination[0] = PathInternal.DirectorySeparatorChar;
753+
destination = destination.Slice(1);
754+
}
755+
756+
ReadOnlySpan<char> fourth = *(ReadOnlySpan<char>*)state.FourthRos;
757+
Debug.Assert(fourth.Length == destination.Length);
758+
fourth.CopyTo(destination);
759+
});
760+
#pragma warning restore CS8500
815761
}
816762

817763
private static ReadOnlySpan<byte> Base32Char => "abcdefghijklmnopqrstuvwxyz012345"u8;

src/libraries/System.Private.CoreLib/src/System/String.Manipulation.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,34 @@ public static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, Re
379379
return result;
380380
}
381381

382+
internal static string Concat(ReadOnlySpan<char> str0, ReadOnlySpan<char> str1, ReadOnlySpan<char> str2, ReadOnlySpan<char> str3, ReadOnlySpan<char> str4)
383+
{
384+
int length = checked(str0.Length + str1.Length + str2.Length + str3.Length + str4.Length);
385+
if (length == 0)
386+
{
387+
return Empty;
388+
}
389+
390+
string result = FastAllocateString(length);
391+
Span<char> resultSpan = new Span<char>(ref result._firstChar, result.Length);
392+
393+
str0.CopyTo(resultSpan);
394+
resultSpan = resultSpan.Slice(str0.Length);
395+
396+
str1.CopyTo(resultSpan);
397+
resultSpan = resultSpan.Slice(str1.Length);
398+
399+
str2.CopyTo(resultSpan);
400+
resultSpan = resultSpan.Slice(str2.Length);
401+
402+
str3.CopyTo(resultSpan);
403+
resultSpan = resultSpan.Slice(str3.Length);
404+
405+
str4.CopyTo(resultSpan);
406+
407+
return result;
408+
}
409+
382410
public static string Concat(params string?[] values)
383411
{
384412
ArgumentNullException.ThrowIfNull(values);

src/libraries/System.Private.Uri/src/System/Uri.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3840,15 +3840,13 @@ private static unsafe ParsingError CheckSchemeSyntax(ReadOnlySpan<char> span, re
38403840
}
38413841

38423842
// Then look up the syntax in a string-based table.
3843-
string str;
3844-
fixed (char* pSpan = span)
3843+
#pragma warning disable CS8500 // takes address of managed type
3844+
string str = string.Create(span.Length, (IntPtr)(&span), (buffer, spanPtr) =>
38453845
{
3846-
str = string.Create(span.Length, (ip: (IntPtr)pSpan, length: span.Length), (buffer, state) =>
3847-
{
3848-
int charsWritten = new ReadOnlySpan<char>((char*)state.ip, state.length).ToLowerInvariant(buffer);
3849-
Debug.Assert(charsWritten == buffer.Length);
3850-
});
3851-
}
3846+
int charsWritten = (*(ReadOnlySpan<char>*)spanPtr).ToLowerInvariant(buffer);
3847+
Debug.Assert(charsWritten == buffer.Length);
3848+
});
3849+
#pragma warning restore CS8500
38523850
syntax = UriParser.FindOrFetchAsUnknownV1Syntax(str);
38533851
return ParsingError.None;
38543852
}

src/libraries/System.Private.Uri/src/System/UriHelper.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -543,22 +543,21 @@ internal static unsafe string StripBidiControlCharacters(ReadOnlySpan<char> strT
543543
return backingString ?? new string(strToClean);
544544
}
545545

546-
fixed (char* pStrToClean = &MemoryMarshal.GetReference(strToClean))
546+
#pragma warning disable CS8500 // takes address of managed type
547+
return string.Create(strToClean.Length - charsToRemove, (IntPtr)(&strToClean), static (buffer, strToCleanPtr) =>
547548
{
548-
return string.Create(strToClean.Length - charsToRemove, (StrToClean: (IntPtr)pStrToClean, strToClean.Length), static (buffer, state) =>
549+
var strToClean = *(ReadOnlySpan<char>*)strToCleanPtr;
550+
int destIndex = 0;
551+
foreach (char c in strToClean)
549552
{
550-
var strToClean = new ReadOnlySpan<char>((char*)state.StrToClean, state.Length);
551-
int destIndex = 0;
552-
foreach (char c in strToClean)
553+
if (!IsBidiControlCharacter(c))
553554
{
554-
if (!IsBidiControlCharacter(c))
555-
{
556-
buffer[destIndex++] = c;
557-
}
555+
buffer[destIndex++] = c;
558556
}
559-
Debug.Assert(buffer.Length == destIndex);
560-
});
561-
}
557+
}
558+
Debug.Assert(buffer.Length == destIndex);
559+
});
560+
#pragma warning restore CS8500
562561
}
563562
}
564563
}

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/PemEncoding.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -572,26 +572,24 @@ public static unsafe string WriteString(ReadOnlySpan<char> label, ReadOnlySpan<b
572572

573573
int encodedSize = GetEncodedSize(label.Length, data.Length);
574574

575-
fixed (char* pLabel = label)
576-
fixed (byte* pData = data)
577-
{
578-
return string.Create(
579-
encodedSize,
580-
(LabelPointer : new IntPtr(pLabel), LabelLength : label.Length, DataPointer : new IntPtr(pData), DataLength : data.Length),
581-
static (destination, state) =>
582-
{
583-
ReadOnlySpan<byte> data = new ReadOnlySpan<byte>(state.DataPointer.ToPointer(), state.DataLength);
584-
ReadOnlySpan<char> label = new ReadOnlySpan<char>(state.LabelPointer.ToPointer(), state.LabelLength);
575+
#pragma warning disable CS8500 // takes address of managed type
576+
return string.Create(
577+
encodedSize,
578+
(LabelPointer: (IntPtr)(&label), DataPointer: (IntPtr)(&data)),
579+
static (destination, state) =>
580+
{
581+
ReadOnlySpan<char> label = *(ReadOnlySpan<char>*)state.LabelPointer;
582+
ReadOnlySpan<byte> data = *(ReadOnlySpan<byte>*)state.DataPointer;
585583

586-
int charsWritten = WriteCore(label, data, destination);
584+
int charsWritten = WriteCore(label, data, destination);
587585

588-
if (charsWritten != destination.Length)
589-
{
590-
Debug.Fail("WriteCore wrote the wrong amount of data");
591-
throw new CryptographicException();
592-
}
593-
});
594-
}
586+
if (charsWritten != destination.Length)
587+
{
588+
Debug.Fail("WriteCore wrote the wrong amount of data");
589+
throw new CryptographicException();
590+
}
591+
});
592+
#pragma warning restore CS8500
595593
}
596594
}
597595
}

0 commit comments

Comments
 (0)