Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Use safe stackalloc where possible (#1742)
Browse files Browse the repository at this point in the history
* Use safe stackalloc where possible

* some more changes

* CR feedback
  • Loading branch information
VSadov authored and KrzysztofCwalina committed Sep 7, 2017
1 parent 27d23e5 commit 0339860
Show file tree
Hide file tree
Showing 11 changed files with 109 additions and 163 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@ public static bool TryWrite(Span<byte> output, Sha256 hash, string keyType, stri
int written, consumed, totalWritten = 0;
bytesWritten = 0;

Span<byte> buffer;
unsafe
{
var pBuffer = stackalloc byte[AuthenticationHeaderBufferSize];
buffer = new Span<byte>(pBuffer, AuthenticationHeaderBufferSize);
}
Span<byte> buffer = stackalloc byte[AuthenticationHeaderBufferSize];

s_type.CopyTo(buffer);
totalWritten += s_type.Length;
Expand Down
5 changes: 2 additions & 3 deletions src/System.Azure.Experimental/System/Azure/Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
namespace System.Azure.Authentication
{
public static class Key {
public unsafe static byte[] ComputeKeyBytes(string key)
public static byte[] ComputeKeyBytes(string key)
{
const int bufferLength = 128;

byte* pBuffer = stackalloc byte[bufferLength];
int written, consumed;
var buffer = new Span<byte>(pBuffer, bufferLength);
Span<byte> buffer = stackalloc byte[bufferLength];
if (Utf16.ToUtf8(key.AsReadOnlySpan().AsBytes(), buffer, out consumed, out written) != OperationStatus.Done)
{
throw new NotImplementedException("need to resize buffer");
Expand Down
32 changes: 11 additions & 21 deletions src/System.Buffers.Experimental/System/Buffers/BufferExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ public static class BufferExtensions
public static void Pipe(this Transformation transformation, ReadOnlyBytes source, IOutput destination)
{
int afterMergeSlice = 0;
ReadOnlySpan<byte> remainder;
Span<byte> stackSpan;

unsafe
{
byte* stackBytes = stackalloc byte[stackLength];
stackSpan = new Span<byte>(stackBytes, stackLength);
}
// TODO: "ReadOnlySpan<byte> remainder = stackalloc byte[0]" would fit better here,
// but it emits substandard IL, see https://github.com/dotnet/roslyn/issues/21952
//
// Assign 'remainder' to something formally stack-referring.
// The default classification is "returnable, not referring to stack", we want the opposite in this case.
ReadOnlySpan<byte> remainder = true ? new ReadOnlySpan<byte>() : stackalloc byte[0];
Span<byte> stackSpan = stackalloc byte[stackLength];

var poisition = Position.First;
while (source.TryGet(ref poisition, out var sourceBuffer, true))
Expand Down Expand Up @@ -325,21 +325,11 @@ internal static int IndexOfStraddling(this ReadOnlySpan<byte> first, IReadOnlyBu
// this check is a small optimization: if the first byte from the value does not exist in the bytesToSearchAgain, there is no reason to combine
if (bytesToSearchAgain.IndexOf(value[0]) != -1)
{
Span<byte> combined;
var combinedBufferLength = value.Length << 1;
if (combinedBufferLength < 128)
{
unsafe
{
byte* temp = stackalloc byte[combinedBufferLength];
combined = new Span<byte>(temp, combinedBufferLength);
}
}
else
{
// TODO (pri 3): I think this could be eliminated by chunking values
combined = new byte[combinedBufferLength];
}
var combined = combinedBufferLength < 128 ?
stackalloc byte[combinedBufferLength] :
// TODO (pri 3): I think this could be eliminated by chunking values
(Span<byte>)new byte[combinedBufferLength];

bytesToSearchAgain.CopyTo(combined);
int combinedLength = bytesToSearchAgain.Length + rest.CopyTo(combined.Slice(bytesToSearchAgain.Length));
Expand Down
50 changes: 16 additions & 34 deletions src/System.Buffers.Experimental/System/Buffers/BytesReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,11 @@ int IndexOfStraddling(ReadOnlySpan<byte> value)

if (bytesToSearchAgain.IndexOf(value[0]) != -1)
{
Span<byte> tempSpan;
var tempLen = value.Length << 1;
if (tempLen < 128)
{
unsafe
{
byte* temp = stackalloc byte[tempLen];
tempSpan = new Span<byte>(temp, tempLen);
}
}
else
{
// TODO (pri 3): I think this could be imporved by chunking values
tempSpan = new byte[tempLen];
}
var tempSpan = tempLen < 128 ?
(Span<byte>)stackalloc byte[tempLen] :
// TODO (pri 3): I think this could be imporved by chunking values
new byte[tempLen];

bytesToSearchAgain.CopyTo(tempSpan);
rest.CopyTo(tempSpan.Slice(bytesToSearchAgain.Length));
Expand Down Expand Up @@ -305,17 +295,13 @@ public bool TryParseBoolean(out bool value)
}
}

unsafe
{
byte* temp = stackalloc byte[15];
var tempSpan = new Span<byte>(temp, 15);
var copied = CopyTo(tempSpan);
Span<byte> tempSpan = stackalloc byte[15];
var copied = CopyTo(tempSpan);

if (PrimitiveParser.TryParseBoolean(tempSpan.Slice(0, copied), out value, out consumed, _symbolTable))
{
Advance(consumed);
return true;
}
if (PrimitiveParser.TryParseBoolean(tempSpan.Slice(0, copied), out value, out consumed, _symbolTable))
{
Advance(consumed);
return true;
}

return false;
Expand All @@ -334,17 +320,13 @@ public bool TryParseUInt64(out ulong value)
}
}

unsafe
{
byte* temp = stackalloc byte[32];
var tempSpan = new Span<byte>(temp, 32);
var copied = CopyTo(tempSpan);
Span<byte> tempSpan = stackalloc byte[32];
var copied = CopyTo(tempSpan);

if (PrimitiveParser.TryParseUInt64(tempSpan.Slice(0, copied), out value, out consumed, 'G', _symbolTable))
{
Advance(consumed);
return true;
}
if (PrimitiveParser.TryParseUInt64(tempSpan.Slice(0, copied), out value, out consumed, 'G', _symbolTable))
{
Advance(consumed);
return true;
}

return false;
Expand Down
10 changes: 4 additions & 6 deletions src/System.IO.Pipelines.Extensions/ReadWriteExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,16 @@ public static T ReadLittleEndian<[Primitive]T>(this ReadableBuffer buffer) where
return value;
}

private static unsafe T ReadMultiBig<[Primitive]T>(ReadableBuffer buffer, int len) where T : struct
private static T ReadMultiBig<[Primitive]T>(ReadableBuffer buffer, int len) where T : struct
{
byte* local = stackalloc byte[len];
var localSpan = new Span<byte>(local, len);
Span<byte> localSpan = stackalloc byte[len];
buffer.Slice(0, len).CopyTo(localSpan);
return localSpan.ReadBigEndian<T>();
}

private static unsafe T ReadMultiLittle<[Primitive]T>(ReadableBuffer buffer, int len) where T : struct
private static T ReadMultiLittle<[Primitive]T>(ReadableBuffer buffer, int len) where T : struct
{
byte* local = stackalloc byte[len];
var localSpan = new Span<byte>(local, len);
Span<byte> localSpan = stackalloc byte[len];
buffer.Slice(0, len).CopyTo(localSpan);
return localSpan.ReadLittleEndian<T>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,24 +203,27 @@ public unsafe static string GetAsciiString(this ReadableBuffer buffer)
/// Decodes the utf8 encoded bytes in the <see cref="ReadableBuffer"/> into a <see cref="string"/>
/// </summary>
/// <param name="buffer">The buffer to decode</param>
public static unsafe string GetUtf8String(this ReadableBuffer buffer)
public static string GetUtf8String(this ReadableBuffer buffer)
{
if (buffer.IsEmpty)
{
return null;
}

ReadOnlySpan<byte> textSpan;
// TODO: "ReadOnlySpan<byte> textSpan = stackalloc byte[0]" would fit better here,
// but it emits substandard IL, see https://github.com/dotnet/roslyn/issues/21952
//
// Assign 'textSpan' to something formally stack-referring.
// The default classification is "returnable, not referring to stack", we want the opposite in this case.
ReadOnlySpan<byte> textSpan = true? new ReadOnlySpan<byte>() : stackalloc byte[0];

if (buffer.IsSingleSpan)
{
textSpan = buffer.First.Span;
}
else if (buffer.Length < 128) // REVIEW: What's a good number
{
var data = stackalloc byte[128];
var destination = new Span<byte>(data, 128);

Span<byte> destination = stackalloc byte[128];
buffer.CopyTo(destination);

// We are able to cast because buffer.Length < 128
Expand Down
123 changes: 60 additions & 63 deletions src/System.Text.Formatting/System/Text/Parsing/SequenceParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,47 +37,48 @@ public static bool TryParseUInt64<T>(this T bufferSequence, out ulong value, out
}

// Combine the first, the second, and potentially more segments into a stack allocated buffer
ReadOnlySpan<byte> combinedSpan;
unsafe
if (first.Length < StackBufferSize)
{
if (first.Length < StackBufferSize) {
var data = stackalloc byte[StackBufferSize];
var destination = new Span<byte>(data, StackBufferSize);

first.CopyTo(destination);
var free = destination.Slice(first.Length);

if (second.Length > free.Length) second = second.Slice(0, free.Length);
second.CopyTo(free);
free = free.Slice(second.Length);

ReadOnlyBuffer<byte> next;
while (free.Length > 0) {
if (bufferSequence.TryGet(ref position, out next)) {
if (next.Length > free.Length) next = next.Slice(0, free.Length);
next.CopyTo(free);
free = free.Slice(next.Length);
}
else {
break;
}
Span<byte> destination = stackalloc byte[StackBufferSize];

first.CopyTo(destination);
var free = destination.Slice(first.Length);

if (second.Length > free.Length) second = second.Slice(0, free.Length);
second.CopyTo(free);
free = free.Slice(second.Length);

ReadOnlyBuffer<byte> next;
while (free.Length > 0)
{
if (bufferSequence.TryGet(ref position, out next))
{
if (next.Length > free.Length) next = next.Slice(0, free.Length);
next.CopyTo(free);
free = free.Slice(next.Length);
}
else
{
break;
}
}

combinedSpan = destination.Slice(0, StackBufferSize - free.Length);
var combinedStackSpan = destination.Slice(0, StackBufferSize - free.Length);

// if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success.
if (PrimitiveParser.InvariantUtf8.TryParseUInt64(combinedSpan, out value, out consumed)) {
if(consumed < combinedSpan.Length || combinedSpan.Length < StackBufferSize) {
return true;
}
// if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success.
if (PrimitiveParser.InvariantUtf8.TryParseUInt64(combinedStackSpan, out value, out consumed))
{
if (consumed < combinedStackSpan.Length || combinedStackSpan.Length < StackBufferSize)
{
return true;
}
}
}

// for invariant culture, we should never reach this point, as invariant uint text is never longer than 127 bytes.
// I left this code here, as we will need it for custom cultures and possibly when we shrink the stack allocated buffer.
combinedSpan = bufferSequence.ToSpan();
if (!PrimitiveParser.InvariantUtf8.TryParseUInt64(first.Span, out value, out consumed)) {
var combinedSpan = bufferSequence.ToSpan();
if (!PrimitiveParser.InvariantUtf8.TryParseUInt64(combinedSpan, out value, out consumed)) {
return false;
}
return true;
Expand Down Expand Up @@ -110,50 +111,46 @@ public static bool TryParseUInt32<T>(this T bufferSequence, out uint value, out
}

// Combine the first, the second, and potentially more segments into a stack allocated buffer
ReadOnlySpan<byte> combinedSpan;
unsafe
{
if (first.Length < StackBufferSize) {
var data = stackalloc byte[StackBufferSize];
var destination = new Span<byte>(data, StackBufferSize);

first.CopyTo(destination);
var free = destination.Slice(first.Length);

if (second.Length > free.Length) second = second.Slice(0, free.Length);
second.CopyTo(free);
free = free.Slice(second.Length);

ReadOnlyBuffer<byte> next;
while (free.Length > 0) {
if (bufferSequence.TryGet(ref position, out next)) {
if (next.Length > free.Length) next = next.Slice(0, free.Length);
next.CopyTo(free);
free = free.Slice(next.Length);
}
else {
break;
}
if (first.Length < StackBufferSize) {

Span<byte> destination = stackalloc byte[StackBufferSize];

first.CopyTo(destination);
var free = destination.Slice(first.Length);

if (second.Length > free.Length) second = second.Slice(0, free.Length);
second.CopyTo(free);
free = free.Slice(second.Length);

ReadOnlyBuffer<byte> next;
while (free.Length > 0) {
if (bufferSequence.TryGet(ref position, out next)) {
if (next.Length > free.Length) next = next.Slice(0, free.Length);
next.CopyTo(free);
free = free.Slice(next.Length);
}
else {
break;
}
}

combinedSpan = destination.Slice(0, StackBufferSize - free.Length);
var combinedStackSpan = destination.Slice(0, StackBufferSize - free.Length);

// if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success.
if (PrimitiveParser.InvariantUtf8.TryParseUInt32(combinedSpan, out value, out consumed)) {
if(consumed < combinedSpan.Length || combinedSpan.Length < StackBufferSize) {
return true;
}
// if the stack allocated buffer parsed succesfully (and for uint it should always do), then return success.
if (PrimitiveParser.InvariantUtf8.TryParseUInt32(combinedStackSpan, out value, out consumed)) {
if(consumed < combinedStackSpan.Length || combinedStackSpan.Length < StackBufferSize) {
return true;
}
}
}

// for invariant culture, we should never reach this point, as invariant uint text is never longer than 127 bytes.
// I left this code here, as we will need it for custom cultures and possibly when we shrink the stack allocated buffer.
combinedSpan = bufferSequence.ToSpan();
var combinedSpan = bufferSequence.ToSpan();
if (!PrimitiveParser.InvariantUtf8.TryParseUInt32(combinedSpan, out value, out consumed)) {
return false;
}
return true;
}
}
}
}
10 changes: 4 additions & 6 deletions src/System.Text.Http.Parser/HttpUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,20 @@ private static string[] CreateMethodNames()
return methodNames;
}

private unsafe static ulong GetAsciiStringAsLong(string str)
private static ulong GetAsciiStringAsLong(string str)
{
Debug.Assert(str.Length == 8, "String must be exactly 8 (ASCII) characters long.");

var buffer = stackalloc byte[8];
Span<byte> span = new Span<byte>(buffer, 8);
Span<byte> span = stackalloc byte[8];
Encoders.Utf16.ToUtf8(str.AsReadOnlySpan().AsBytes(), span, out int consumed, out int written);
return span.Read<ulong>();
}

private unsafe static uint GetAsciiStringAsInt(string str)
private static uint GetAsciiStringAsInt(string str)
{
Debug.Assert(str.Length == 4, "String must be exactly 4 (ASCII) characters long.");

var buffer = stackalloc byte[4];
Span<byte> span = new Span<byte>(buffer, 4);
Span<byte> span = stackalloc byte[4];
Encoders.Utf16.ToUtf8(str.AsReadOnlySpan().AsBytes(), span, out int consumed, out int written);
return span.Read<uint>();
}
Expand Down
Loading

0 comments on commit 0339860

Please sign in to comment.