Skip to content

Commit d6d58d0

Browse files
committed
Use ASCII.GetBytes
1 parent 5583d91 commit d6d58d0

File tree

5 files changed

+47
-177
lines changed

5 files changed

+47
-177
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11538,7 +11538,7 @@ internal unsafe void CopyToFast(ref BufferWriter<PipeWriter> output)
1153811538
if (value != null)
1153911539
{
1154011540
output.Write(headerKey);
11541-
output.WriteAsciiNoValidation(value);
11541+
output.WriteAscii(value);
1154211542
}
1154311543
}
1154411544
}

src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ static void CopyExtraHeaders(ref BufferWriter<PipeWriter> buffer, Dictionary<str
4848
if (value != null)
4949
{
5050
buffer.Write(CrLf);
51-
buffer.WriteAsciiNoValidation(kv.Key);
51+
buffer.WriteAscii(kv.Key);
5252
buffer.Write(ColonSpace);
53-
buffer.WriteAsciiNoValidation(value);
53+
buffer.WriteAscii(value);
5454
}
5555
}
5656
}

src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ public void EncodesAsAscii(string input, byte[] expected)
8787
{
8888
var pipeWriter = _pipe.Writer;
8989
var writer = new BufferWriter<PipeWriter>(pipeWriter);
90-
writer.WriteAsciiNoValidation(input);
90+
writer.WriteAscii(input);
9191
writer.Commit();
9292
pipeWriter.FlushAsync().GetAwaiter().GetResult();
9393
pipeWriter.Complete();
@@ -111,13 +111,13 @@ public void EncodesAsAscii(string input, byte[] expected)
111111
[InlineData("𤭢𐐝")]
112112
// non-ascii characters stored in 16 bits
113113
[InlineData("ñ٢⛄⛵")]
114-
public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input)
114+
public void WriteAsciiWritesOnlyOneBytePerChar(string input)
115115
{
116116
// WriteAscii doesn't validate if characters are in the ASCII range
117117
// but it shouldn't produce more than one byte per character
118118
var writerBuffer = _pipe.Writer;
119119
var writer = new BufferWriter<PipeWriter>(writerBuffer);
120-
writer.WriteAsciiNoValidation(input);
120+
writer.WriteAscii(input);
121121
writer.Commit();
122122
writerBuffer.FlushAsync().GetAwaiter().GetResult();
123123
var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult();
@@ -126,14 +126,14 @@ public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input)
126126
}
127127

128128
[Fact]
129-
public void WriteAsciiNoValidation()
129+
public void WriteAscii()
130130
{
131131
const byte maxAscii = 0x7f;
132132
var writerBuffer = _pipe.Writer;
133133
var writer = new BufferWriter<PipeWriter>(writerBuffer);
134134
for (var i = 0; i < maxAscii; i++)
135135
{
136-
writer.WriteAsciiNoValidation(new string((char)i, 1));
136+
writer.WriteAscii(new string((char)i, 1));
137137
}
138138
writer.Commit();
139139
writerBuffer.FlushAsync().GetAwaiter().GetResult();
@@ -167,7 +167,7 @@ public void WritesAsciiAcrossBlockBoundaries(int stringLength, int gapSize)
167167
Assert.Equal(gapSize, writer.Span.Length);
168168

169169
var bufferLength = writer.Span.Length;
170-
writer.WriteAsciiNoValidation(testString);
170+
writer.WriteAscii(testString);
171171
Assert.NotEqual(bufferLength, writer.Span.Length);
172172
writer.Commit();
173173
writerBuffer.FlushAsync().GetAwaiter().GetResult();

src/Servers/Kestrel/shared/KnownHeaders.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ internal unsafe void CopyToFast(ref BufferWriter<PipeWriter> output)
945945
if (value != null)
946946
{{
947947
output.Write(headerKey);
948-
output.WriteAsciiNoValidation(value);
948+
output.WriteAscii(value);
949949
}}
950950
}}
951951
}}

src/Shared/ServerInfrastructure/BufferExtensions.cs

Lines changed: 37 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
5-
using System.Buffers;
64
using System.Diagnostics;
75
using System.IO.Pipelines;
86
using System.Runtime.CompilerServices;
97
using System.Runtime.InteropServices;
10-
using System.Runtime.Intrinsics.X86;
8+
using System.Text;
119

1210
namespace System.Buffers
1311
{
@@ -42,27 +40,26 @@ public static ArraySegment<byte> GetArray(this ReadOnlyMemory<byte> memory)
4240
return result;
4341
}
4442

45-
internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter<PipeWriter> buffer, string data)
43+
internal static unsafe void WriteAscii(ref this BufferWriter<PipeWriter> buffer, string data)
4644
{
4745
if (string.IsNullOrEmpty(data))
4846
{
4947
return;
5048
}
5149

52-
var dest = buffer.Span;
53-
var destLength = dest.Length;
54-
var sourceLength = data.Length;
50+
var dataLength = data.Length;
51+
var bytes = buffer.Span;
52+
var bytesLength = bytes.Length;
5553

56-
// Fast path, try copying to the available memory directly
57-
if (sourceLength <= destLength)
54+
// Fast path, try encoding to the available memory directly
55+
if (dataLength <= bytesLength)
5856
{
59-
fixed (char* input = data)
60-
fixed (byte* output = dest)
57+
fixed (char* charsPtr = data)
58+
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
6159
{
62-
EncodeAsciiCharsToBytes(input, output, sourceLength);
60+
Encoding.ASCII.GetBytes(charsPtr, dataLength, bytesPtr, bytesLength);
61+
buffer.Advance(dataLength);
6362
}
64-
65-
buffer.Advance(sourceLength);
6663
}
6764
else
6865
{
@@ -144,170 +141,43 @@ private static void WriteNumericMultiWrite(ref this BufferWriter<PipeWriter> buf
144141
[MethodImpl(MethodImplOptions.NoInlining)]
145142
private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter<PipeWriter> buffer, string data)
146143
{
147-
var remaining = data.Length;
148-
149-
fixed (char* input = data)
150-
{
151-
var inputSlice = input;
152-
153-
while (remaining > 0)
154-
{
155-
var writable = Math.Min(remaining, buffer.Span.Length);
156-
157-
if (writable == 0)
158-
{
159-
buffer.Ensure();
160-
continue;
161-
}
162-
163-
fixed (byte* output = buffer.Span)
164-
{
165-
EncodeAsciiCharsToBytes(inputSlice, output, writable);
166-
}
167-
168-
inputSlice += writable;
169-
remaining -= writable;
170-
171-
buffer.Advance(writable);
172-
}
173-
}
174-
}
175-
176-
private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length)
177-
{
178-
// Note: Not BIGENDIAN or check for non-ascii
179-
if (Bmi2.IsSupported)
180-
{
181-
if (length < 4)
182-
{
183-
// Convert the chars to bytes one by one if there are less than 4.
184-
for (int i = 0; i < length; i++)
185-
{
186-
char ch = input[i];
187-
output[i] = (byte)ch; // Cast convert
188-
}
189-
}
190-
else if (Bmi2.X64.IsSupported) // 64-bit, 4+ chars
191-
{
192-
// Convert all the 4 char sequences, except final 1 - 4 char sequence.
193-
int firstLength = length - sizeof(int);
194-
Debug.Assert(firstLength >= 0);
195-
for (int i = 0; i < firstLength; i += sizeof(int))
196-
{
197-
*(uint*)(output + i) = (uint)Bmi2.X64.ParallelBitExtract(
198-
*(ulong*)(input + i),
199-
0x00FF00FF_00FF00FFul);
200-
}
144+
Debug.Assert(!string.IsNullOrEmpty(data));
201145

202-
// Convert the final sequence of 4 from the end.
203-
// This may overlap with the last sequence of the loop, if length is not a multiple of 4.
204-
*(uint*)(output + firstLength) = (uint)Bmi2.X64.ParallelBitExtract(
205-
*(ulong*)(input + firstLength),
206-
0x00FF00FF_00FF00FFul);
207-
}
208-
else // 32-bit, 4+ chars
209-
{
210-
// Convert all the 2 char sequences, except final 1 - 2 char sequence
211-
int firstLength = length - sizeof(ushort);
212-
Debug.Assert(firstLength >= 0);
213-
for (int i = 0; i < firstLength; i += sizeof(ushort))
214-
{
215-
*(ushort*)(output + i) = (ushort)Bmi2.ParallelBitExtract(
216-
*(uint*)(input + i),
217-
0x00FF00FFu);
218-
}
219-
220-
// Convert the final sequence of 2 from the end.
221-
// This may overlap with the last sequence of the loop, if length is not a multiple of 2
222-
*(ushort*)(output + firstLength) = (ushort)Bmi2.ParallelBitExtract(
223-
*(uint*)(input + firstLength),
224-
0x00FF00FFu);
225-
}
226-
}
227-
else
146+
fixed (char* charsPtr = data)
228147
{
229-
const int Shift16Shift24 = (1 << 16) | (1 << 24);
230-
const int Shift8Identity = (1 << 8) | (1);
231-
232-
int i = 0;
233-
// Use Intrinsic switch
234-
if (IntPtr.Size == 8) // 64 bit
148+
var dataLength = data.Length;
149+
var offset = 0;
150+
var bytes = buffer.Span;
151+
var bytesLength = bytes.Length;
152+
do
235153
{
236-
if (length < 4) goto trailing;
237-
238-
int unaligned = (int)(((ulong)input) & 0x7) >> 1;
239-
// Unaligned chars
240-
for (; i < unaligned; i++)
241-
{
242-
char ch = input[i];
243-
output[i] = (byte)ch; // Cast convert
244-
}
245-
246-
// Aligned
247-
int ulongDoubleCount = (length - i) & ~0x7;
248-
for (; i < ulongDoubleCount; i += 8)
154+
var writable = Math.Min(dataLength - offset, bytesLength);
155+
// Zero length spans are possible
156+
if (writable > 0)
249157
{
250-
ulong inputUlong0 = *(ulong*)(input + i);
251-
ulong inputUlong1 = *(ulong*)(input + i + 4);
252-
// Pack 16 ASCII chars into 16 bytes
253-
*(uint*)(output + i) =
254-
((uint)((inputUlong0 * Shift16Shift24) >> 24) & 0xffff) |
255-
((uint)((inputUlong0 * Shift8Identity) >> 24) & 0xffff0000);
256-
*(uint*)(output + i + 4) =
257-
((uint)((inputUlong1 * Shift16Shift24) >> 24) & 0xffff) |
258-
((uint)((inputUlong1 * Shift8Identity) >> 24) & 0xffff0000);
259-
}
260-
if (length - 4 > i)
261-
{
262-
ulong inputUlong = *(ulong*)(input + i);
263-
// Pack 8 ASCII chars into 8 bytes
264-
*(uint*)(output + i) =
265-
((uint)((inputUlong * Shift16Shift24) >> 24) & 0xffff) |
266-
((uint)((inputUlong * Shift8Identity) >> 24) & 0xffff0000);
267-
i += 4;
268-
}
158+
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
159+
{
160+
Encoding.ASCII.GetBytes(charsPtr + offset, writable, bytesPtr, bytesLength);
269161

270-
trailing:
271-
for (; i < length; i++)
272-
{
273-
char ch = input[i];
274-
output[i] = (byte)ch; // Cast convert
275-
}
276-
}
277-
else // 32 bit
278-
{
279-
// Unaligned chars
280-
if ((unchecked((int)input) & 0x2) != 0)
281-
{
282-
char ch = *input;
283-
i = 1;
284-
output[0] = (byte)ch; // Cast convert
162+
buffer.Advance(writable);
163+
offset += writable;
164+
}
285165
}
286166

287-
// Aligned
288-
int uintCount = (length - i) & ~0x3;
289-
for (; i < uintCount; i += 4)
290-
{
291-
uint inputUint0 = *(uint*)(input + i);
292-
uint inputUint1 = *(uint*)(input + i + 2);
293-
// Pack 4 ASCII chars into 4 bytes
294-
*(ushort*)(output + i) = (ushort)(inputUint0 | (inputUint0 >> 8));
295-
*(ushort*)(output + i + 2) = (ushort)(inputUint1 | (inputUint1 >> 8));
296-
}
297-
if (length - 1 > i)
167+
// Get new span if more to encode, and reset bytesLength
168+
if (offset < dataLength)
298169
{
299-
uint inputUint = *(uint*)(input + i);
300-
// Pack 2 ASCII chars into 2 bytes
301-
*(ushort*)(output + i) = (ushort)(inputUint | (inputUint >> 8));
302-
i += 2;
170+
buffer.Ensure();
171+
bytes = buffer.Span;
172+
bytesLength = bytes.Length;
173+
continue;
303174
}
304-
305-
if (i < length)
175+
else
306176
{
307-
char ch = input[i];
308-
output[i] = (byte)ch; // Cast convert
177+
// Encoded everything
178+
break;
309179
}
310-
}
180+
} while (true);
311181
}
312182
}
313183

0 commit comments

Comments
 (0)