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

Use safe stackalloc where possible #1742

Merged
merged 3 commits into from
Sep 7, 2017
Merged

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Sep 7, 2017

No description provided.

}
}
}

// 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)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was 'first.Span' a bug here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure but it seems like it given combinedSpan is assigned and then not used.
cc @KrzysztofCwalina

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not be shocked if this codepath never executed (as the comment implies)

@VSadov
Copy link
Member Author

VSadov commented Sep 7, 2017

@KrzysztofCwalina @ahsonkhan @shiftylogic - please review.

@VSadov
Copy link
Member Author

VSadov commented Sep 7, 2017

@stephentoub @jaredpar - FYI. Using safe stackalloc in CoreFxLab.

@ahsonkhan
Copy link
Contributor

@dotnet-bot test Innerloop OSX10.12 Debug Build and Test

// make 'remainder' formally stack-referring or we won't be able to reference stack data later
ReadOnlySpan<byte> remainder = stackalloc byte[0];
var remainder = true ? new ReadOnlySpan<byte>() : stackalloc byte[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: change from var to ReadOnlySpan, esp since in the future, the r.h.s would look like stackalloc byte[0];

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. I was not sure if mentioning the type twice would be too verbose.

// TODO: "ReadOnlySpan<byte> textSpan = stackalloc byte[0]" would fit better here,
// but it emits substandard IL, see https://github.com/dotnet/roslyn/issues/21952
//
// make 'textSpan' formally stack-referring or we won't be able to reference stack data later
Copy link
Contributor

@ahsonkhan ahsonkhan Sep 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this comment.

What does it mean by 'formally stack-referring'. Also, shouldn't it read:
make 'textSpan' formally stack-referring or we will be able to reference stack data later

If textSpan is a stackalloc variable, we can't reference it outside the method, correct? The comment seems to suggest that if we don't make it 'stack-referring', we won't be able to reference it later, which has the opposite meaning.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do not assign anything, 'textSpan' will default to be a returnable variable. We will be able to return it from the method, but will not be able to assign stackalloc'd spans. That is what you normally want, but not in this case. Here we need to initialize to something stackallocated to intentionally "taint" the variable as "possibly referring to stack data".

'stackalloc byte[0]' would be a nice choice since it is basically the same as 'default(Span)', but formally stack-allocated. However we do a poor job with optimizing such trivial case, so I am using a more verbose, but reliable, way to essentially assign a default span, while forcing the variable be classified as stack-referring.

I will try to change the comment to be more clear.

Copy link
Contributor

@ahsonkhan ahsonkhan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@davidfowl
Copy link
Member

This is pretty nice cleanup

Span<byte> combined = combinedBufferLength < 128 ?
stackalloc byte[combinedBufferLength] :
// TODO (pri 3): I think this could be eliminated by chunking values
combined = new byte[combinedBufferLength];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this "combined =" on purpose or a mistake?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo, it is a redundant assignment

var pBuffer = stackalloc byte[AuthenticationHeaderBufferSize];
buffer = new Span<byte>(pBuffer, AuthenticationHeaderBufferSize);
}
Span<byte> buffer = stackalloc byte[AuthenticationHeaderBufferSize];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

// TODO (pri 3): I think this could be eliminated by chunking values
combined = new byte[combinedBufferLength];
}
var combined = combinedBufferLength < 128 ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should settle on the following idiom:

Span<byte> buffer = size < 128 ? stackalloc byte[size] : new byte[size];

.. as opposed to assigning to var and then having to cast.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it stands now the dominant type of a ternary expression is selected from one of consequence or alternative branches.

If you have a span as a type of either of the branches, it will dominate. Otherwise compiler faces an ambiguity since no dominant type can be chosen.

I will bring this up to language folks.
There are some tradeoffs that we can have around the natural type of stackalloc.

// 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];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this be fixed before we ship?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just use ReadOnlySpan.Empty here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KrzysztofCwalina - yes, will fix this before shipping. The trivial stackalloc byte[0] is emitted "correctly", but the IL is embarrassingly redundant, even worse after JIT-ting.

Optimizing this case in C# compiler backend will benefit existing code as well, so the the value vs. effort is very favorable here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shiftylogic - if you use ReadOnlySpan.Empty or any other not stack allocated data to initialize the span you are telling the compiler - "I want to be able to return this from the method or mix with other returnables, please make sure I do not refer to stack data by accident".
That is generally the case.

Here we have a relatively rare opposite - We are slicing through stack allocated data and store in this variable, but have no intent to return it.
stackalloc byte[0] will convey to the compiler - "please allow referring to stack data, make sure I can never return this from the method"

@KrzysztofCwalina
Copy link
Member

Wow, this is such a groundbreaking change to our platform where features in the run-time, libraries, and compilers come together so nicely.

@raja-krishnan, @MeiChin-Tsai

@omariom
Copy link
Contributor

omariom commented Jan 21, 2018

@KrzysztofCwalina

Wow, this is such a groundbreaking change to our platform where features in the run-time, libraries, and compilers come together so nicely.

The most impressive was how quickly the combined efforts of all three teams made it a real thing. A year ago it was a prototype in CorefxLab.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants