diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs index 0abb6da2a0..fff909e41c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs @@ -11538,7 +11538,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (value != null) { output.Write(headerKey); - output.WriteAsciiNoValidation(value); + output.WriteAscii(value); } } } diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs index a87a22f56a..f2100b0cd8 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpResponseHeaders.cs @@ -48,9 +48,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http if (value != null) { buffer.Write(CrLf); - buffer.WriteAsciiNoValidation(kv.Key); + buffer.WriteAscii(kv.Key); buffer.Write(ColonSpace); - buffer.WriteAsciiNoValidation(value); + buffer.WriteAscii(value); } } } diff --git a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs index ebf57aebeb..d95b5c18db 100644 --- a/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs +++ b/src/Servers/Kestrel/Core/test/PipelineExtensionTests.cs @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var pipeWriter = _pipe.Writer; var writer = new BufferWriter(pipeWriter); - writer.WriteAsciiNoValidation(input); + writer.WriteAscii(input); writer.Commit(); pipeWriter.FlushAsync().GetAwaiter().GetResult(); pipeWriter.Complete(); @@ -111,13 +111,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests [InlineData("𤭢𐐝")] // non-ascii characters stored in 16 bits [InlineData("ñ٢⛄⛵")] - public void WriteAsciiNoValidationWritesOnlyOneBytePerChar(string input) + public void WriteAsciiWritesOnlyOneBytePerChar(string input) { // WriteAscii doesn't validate if characters are in the ASCII range // but it shouldn't produce more than one byte per character var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); - writer.WriteAsciiNoValidation(input); + writer.WriteAscii(input); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); var reader = _pipe.Reader.ReadAsync().GetAwaiter().GetResult(); @@ -126,14 +126,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public void WriteAsciiNoValidation() + public void WriteAscii() { const byte maxAscii = 0x7f; var writerBuffer = _pipe.Writer; var writer = new BufferWriter(writerBuffer); for (var i = 0; i < maxAscii; i++) { - writer.WriteAsciiNoValidation(new string((char)i, 1)); + writer.WriteAscii(new string((char)i, 1)); } writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); @@ -167,7 +167,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests Assert.Equal(gapSize, writer.Span.Length); var bufferLength = writer.Span.Length; - writer.WriteAsciiNoValidation(testString); + writer.WriteAscii(testString); Assert.NotEqual(bufferLength, writer.Span.Length); writer.Commit(); writerBuffer.FlushAsync().GetAwaiter().GetResult(); diff --git a/src/Servers/Kestrel/shared/KnownHeaders.cs b/src/Servers/Kestrel/shared/KnownHeaders.cs index 179911a476..fd57db25ae 100644 --- a/src/Servers/Kestrel/shared/KnownHeaders.cs +++ b/src/Servers/Kestrel/shared/KnownHeaders.cs @@ -945,7 +945,7 @@ $@" private void Clear(long bitsToClear) if (value != null) {{ output.Write(headerKey); - output.WriteAsciiNoValidation(value); + output.WriteAscii(value); }} }} }} diff --git a/src/Shared/ServerInfrastructure/BufferExtensions.cs b/src/Shared/ServerInfrastructure/BufferExtensions.cs index 2a4b6d8bbd..34cbeb5357 100644 --- a/src/Shared/ServerInfrastructure/BufferExtensions.cs +++ b/src/Shared/ServerInfrastructure/BufferExtensions.cs @@ -1,11 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Buffers; +using System.Diagnostics; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System.Buffers { @@ -40,7 +40,7 @@ namespace System.Buffers return result; } - internal static unsafe void WriteAsciiNoValidation(ref this BufferWriter buffer, string data) + internal static void WriteAscii(ref this BufferWriter buffer, string data) { if (string.IsNullOrEmpty(data)) { @@ -48,18 +48,11 @@ namespace System.Buffers } var dest = buffer.Span; - var destLength = dest.Length; var sourceLength = data.Length; - - // Fast path, try copying to the available memory directly - if (sourceLength <= destLength) + // Fast path, try encoding to the available memory directly + if (sourceLength <= dest.Length) { - fixed (char* input = data) - fixed (byte* output = dest) - { - EncodeAsciiCharsToBytes(input, output, sourceLength); - } - + Encoding.ASCII.GetBytes(data, dest); buffer.Advance(sourceLength); } else @@ -140,123 +133,31 @@ namespace System.Buffers } [MethodImpl(MethodImplOptions.NoInlining)] - private unsafe static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) + private static void WriteAsciiMultiWrite(ref this BufferWriter buffer, string data) { - var remaining = data.Length; - - fixed (char* input = data) + var dataLength = data.Length; + var offset = 0; + var bytes = buffer.Span; + do { - var inputSlice = input; + var writable = Math.Min(dataLength - offset, bytes.Length); + // Zero length spans are possible, though unlikely. + // ASCII.GetBytes and .Advance will both handle them so we won't special case for them. + Encoding.ASCII.GetBytes(data.AsSpan(offset, writable), bytes); + buffer.Advance(writable); - while (remaining > 0) + offset += writable; + if (offset >= dataLength) { - var writable = Math.Min(remaining, buffer.Span.Length); - - if (writable == 0) - { - buffer.Ensure(); - continue; - } - - fixed (byte* output = buffer.Span) - { - EncodeAsciiCharsToBytes(inputSlice, output, writable); - } - - inputSlice += writable; - remaining -= writable; - - buffer.Advance(writable); - } - } - } - - private static unsafe void EncodeAsciiCharsToBytes(char* input, byte* output, int length) - { - // Note: Not BIGENDIAN or check for non-ascii - const int Shift16Shift24 = (1 << 16) | (1 << 24); - const int Shift8Identity = (1 << 8) | (1); - - // Encode as bytes up to the first non-ASCII byte and return count encoded - int i = 0; - // Use Intrinsic switch - if (IntPtr.Size == 8) // 64 bit - { - if (length < 4) goto trailing; - - int unaligned = (int)(((ulong)input) & 0x7) >> 1; - // Unaligned chars - for (; i < unaligned; i++) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert + Debug.Assert(offset == dataLength); + // Encoded everything + break; } - // Aligned - int ulongDoubleCount = (length - i) & ~0x7; - for (; i < ulongDoubleCount; i += 8) - { - ulong inputUlong0 = *(ulong*)(input + i); - ulong inputUlong1 = *(ulong*)(input + i + 4); - // Pack 16 ASCII chars into 16 bytes - *(uint*)(output + i) = - ((uint)((inputUlong0 * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong0 * Shift8Identity) >> 24) & 0xffff0000); - *(uint*)(output + i + 4) = - ((uint)((inputUlong1 * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong1 * Shift8Identity) >> 24) & 0xffff0000); - } - if (length - 4 > i) - { - ulong inputUlong = *(ulong*)(input + i); - // Pack 8 ASCII chars into 8 bytes - *(uint*)(output + i) = - ((uint)((inputUlong * Shift16Shift24) >> 24) & 0xffff) | - ((uint)((inputUlong * Shift8Identity) >> 24) & 0xffff0000); - i += 4; - } - - trailing: - for (; i < length; i++) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert - } - } - else // 32 bit - { - // Unaligned chars - if ((unchecked((int)input) & 0x2) != 0) - { - char ch = *input; - i = 1; - *(output) = (byte)ch; // Cast convert - } - - // Aligned - int uintCount = (length - i) & ~0x3; - for (; i < uintCount; i += 4) - { - uint inputUint0 = *(uint*)(input + i); - uint inputUint1 = *(uint*)(input + i + 2); - // Pack 4 ASCII chars into 4 bytes - *(ushort*)(output + i) = (ushort)(inputUint0 | (inputUint0 >> 8)); - *(ushort*)(output + i + 2) = (ushort)(inputUint1 | (inputUint1 >> 8)); - } - if (length - 1 > i) - { - uint inputUint = *(uint*)(input + i); - // Pack 2 ASCII chars into 2 bytes - *(ushort*)(output + i) = (ushort)(inputUint | (inputUint >> 8)); - i += 2; - } - - if (i < length) - { - char ch = *(input + i); - *(output + i) = (byte)ch; // Cast convert - } - } + // Get new span, more to encode. + buffer.Ensure(); + bytes = buffer.Span; + } while (true); } private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();