New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mark readonly structs as such #311
Conversation
@@ -12,6 +12,10 @@ Microsoft.Extensions.Primitives.StringSegment</Description> | |||
<PackageTags>primitives</PackageTags> | |||
</PropertyGroup> | |||
|
|||
<PropertyGroup> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should do this globally /cc @natemcmaster @Eilon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has come up a few times lately and the answer I got from @natemcmaster is that we haven't done this because VSCode doesn't have it yet.
It also looks like Omnisharp (the library) added it 5 days ago https://github.com/OmniSharp/omnisharp-roslyn/releases but Omnisharp (the VS Code extension) hasn't yet https://github.com/OmniSharp/omnisharp-vscode/releases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DustinCampbell release a new omnisharp 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll make it our default once we have better tooling support. cref aspnet/BuildTools#566
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's in now.
This isn't a breaking change right? |
Don't think so |
Yes, not breaking dotnet/coreclr#14789 (comment)
|
ref -> in? |
Can revert back to |
Except can't pass this by ref 😢 |
Can't define a second internal internal Enumerator(in StringTokenizer tokenizer)
public Enumerator(ref StringTokenizer tokenizer) |
@@ -57,7 +57,7 @@ public struct Enumerator : IEnumerator<StringSegment> | |||
private readonly char[] _separators; | |||
private int _index; | |||
|
|||
public Enumerator(ref StringTokenizer tokenizer) | |||
public Enumerator(in StringTokenizer tokenizer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a breaking change?
Yea I think this isn't doable without breaking changes. We should make sure it's detected as such with api check. |
Think can do a no-breaking change variant |
Should be ok now? |
Will also mean the enumerator for As it will no longer cause a copy on each iteration and a GC write barrier with it |
And update the methods to take advantage 😄 |
b0fbd7f
to
23a704c
Compare
Your breaking change tool picked up the adding of |
@@ -47,7 +47,7 @@ public unsafe void Append(string s) | |||
Append(s, 0, s.Length); | |||
} | |||
|
|||
public void Append(StringSegment segment) | |||
public void Append(in StringSegment segment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is breaking, it changes the signature to be the equivalent of ref
, but with an additional readonly attribute for compiler disambiguation (the ref changes the signature, the attribute does not)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an additional method was added, which would the compiler prefer, as to the caller they are the same sig?
public void Append(StringSegment segment)
public void Append(in StringSegment segment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems it gets confused
Error CS0121 The call is ambiguous between the following methods or properties:
'StringSegment.Equals(StringSegment)' and 'StringSegment.Equals(in StringSegment)'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "confusion" is getting resolved in 7.3, iirc.
The byval will always win by default and you will need to explicitly specify in
to call the other overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can call the in
without in
when there is only one overload; then it should clearly also win when its a readonly struct
as there is no disadvantage to it winning only an advantage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That depends on the size of the struct, the underlying ABI, and whether or not you want different overload resolution for a normal struct vs a readonly struct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Differing read only vs normal can see as perhaps weird.
As to size and ABI; overload is going to be the same regardless; so really that's a question of whether the overload should have been written in the first place; since it has there must be a reason (unless you are ifdefing it)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And if you are ifdefing it, you just ifdef out the in
overload if that's not suitable for the platform
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was primarily indicating there may be some scenarios where it isn't always advantageous.
As to size and ABI; overload is going to be the same regardless; so really that's a question of whether the overload should have been written in the first place; since it has there must be a reason (unless you are ifdefing it)
Yes, and defaulting to the byval
overload is, IMO, the "better" default (from a language perspective). It means that we don't need another keyword to indicate the byval
overload should be called, it means existing code will continue calling the same overload they did before, and that they can explicitly opt-in to calling the new overload if needed/where desired.
1b1b940
to
5e8bb44
Compare
Should be also binary compat now |
/// <param name="builder">The <see cref="StringBuilder"/> to add to.</param> | ||
/// <param name="segment">The <see cref="StringSegment"/> to add.</param> | ||
/// <returns>The original <see cref="StringBuilder"/>.</returns> | ||
public static StringBuilder Append(this StringBuilder builder, in StringSegment segment) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is a source breaking change until 7.3.
Anyone that was previously calling the public static StringBuilder Append(this StringBuilder builder, StringSegment segment)
overload will now get a CS0121: The call is ambiguous between the following methods or properties
(if they are using the 7.2 version of the compiler).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is also some weirdness in that the v7.1 (or prior) version of the compiler will allow the above, as it will see the new overload as ref
and allow the compiler to call it and disambiguate with ref
, but that will break if recompiled using v7.2+ since ref
cannot be used for in
parameters (I'm going to log a bug for this in a minute).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
29b0afc
to
51db527
Compare
Ok, hopefully its all happily compat now :) |
51db527
to
31a0de4
Compare
Rebased |
@@ -31,21 +43,21 @@ public StringTokenizer(string value, char[] separators) : this((StringSegment)va | |||
/// <param name="separators">The characters to tokenize by.</param> | |||
public StringTokenizer(StringSegment value, char[] separators) | |||
{ | |||
if (value == null) | |||
if (!value.HasValue) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently converts null into a new StringValues(null)
then does ==(StringValues, StringValues)
on it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though this is the first time noticing ExceptionArgument. Not a fan.
Mainly for inlining and code size; and inlined code size repetition, also particularly with generics; this the lightest touch other than combining or passing no arguments; as its just a number: ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.separators); is G_M1150_IG06:
B907000000 mov ecx, 7
E8C8FCFFFF call ThrowHelper:ThrowArgumentNullException(int)
G_M1150_IG07:
B908000000 mov ecx, 8
E8BEFCFFFF call ThrowHelper:ThrowArgumentNullException(int)
CC int3 Passing a string with ThrowHelper.ThrowArgumentNullException(nameof(value));
ThrowHelper.ThrowArgumentNullException(nameof(separators)); is G_M1150_IG06:
48B9683025DE40010000 mov rcx, 0x140DE253068
488B09 mov rcx, gword ptr [rcx]
E8C0FCFFFF call ThrowHelper:ThrowArgumentNullException(ref)
G_M1150_IG07:
48B9703025DE40010000 mov rcx, 0x140DE253070
488B09 mov rcx, gword ptr [rcx]
E8AEFCFFFF call ThrowHelper:ThrowArgumentNullException(ref)
CC int3 Throwing inline however is an entirely different beast: throw new ArgumentNullException(nameof(value));
throw new ArgumentNullException(nameof(separators)); is G_M1150_IG06:
48B9A84C9C89FD7F0000 mov rcx, 0x7FFD899C4CA8
E8D71AB05F call CORINFO_HELP_NEWSFAST
488BF0 mov rsi, rax
B903000000 mov ecx, 3
48BA204DA054FD7F0000 mov rdx, 0x7FFD54A04D20
E86055AE5F call CORINFO_HELP_STRCNS
488BD0 mov rdx, rax
488BCE mov rcx, rsi
E805CE7834 call ArgumentNullException:.ctor(ref):this
488BCE mov rcx, rsi
E81DD8A85F call CORINFO_HELP_THROW
G_M1150_IG07:
48B9A84C9C89FD7F0000 mov rcx, 0x7FFD899C4CA8
E89E1AB05F call CORINFO_HELP_NEWSFAST
488BF0 mov rsi, rax
B90F000000 mov ecx, 15
48BA204DA054FD7F0000 mov rdx, 0x7FFD54A04D20
E82755AE5F call CORINFO_HELP_STRCNS
488BD0 mov rdx, rax
488BCE mov rcx, rsi
E8CCCD7834 call ArgumentNullException:.ctor(ref):this
488BCE mov rcx, rsi
E8E4D7A85F call CORINFO_HELP_THROW
CC int3 |
The Jit won't split functions (except when doing AoT; where it does move the throws out-of-line in the actual assembly); so it would be nice if the C# compiler created ThrowHelpers, but last I asked the feedback was the Jit should do it. The Jit does move the throws (and ThrowHelper calls) to the end of the method and the ifs are unpredicted; so the main cost is lost inlinining opportunities and a CPU instruction cache that's not dense with hot code but has lots of cold non-executed stuff in it. But inlining is the uber optimization; as long as its not bringing lots of junk with it... |
@benaadams can you remove the manual C# 7.2 project file, it should be our default now. |
Removed |
|
||
|
||
<PropertyGroup> | ||
<LangVersion>7.2</LangVersion> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benaadams missed one
Removed :) |
ca5f681
to
4f24d65
Compare
Rebased |
@pakrym can you take a look? |
} | ||
|
||
_value = value; | ||
_separators = separators; | ||
} | ||
|
||
public Enumerator GetEnumerator() => new Enumerator(ref this); | ||
public Enumerator GetEnumerator() => new Enumerator(in _value, _separators); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does in
help codegen in this case? In theory ctor should be inlined and parameter copy eliminated.
I'm fine with this change, I just think that we should start from being conservative with using |
@@ -254,6 +254,44 @@ public static StringValues Concat(StringValues values1, StringValues values2) | |||
return new StringValues(combined); | |||
} | |||
|
|||
public static StringValues Concat(in StringValues values, string value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benaadams Is this for kestrel?
Means they can be used efficiently with
in
Resolves: dotnet/aspnetcore#2856
Resolves: dotnet/aspnetcore#2857
Resolves: dotnet/aspnetcore#2858