-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Collection expression spread optimization #71195
Collection expression spread optimization #71195
Conversation
This is awesome. I think it would be good to have a simple doc that lists the patterns we optimize, and the version of the compiler (maybe) that introduced the optimization. It would be good for people who want to know when this stuff does or doesn't happen. |
60c4efa
to
511526c
Compare
It looks like a change in the base branch is preventing optimized codegen in 7 tests in CollectionExpressionTests, and I forgot to update a VB WellKnownMembers test. I'll look into it tomorrow. |
src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CollectionExpression.cs
Outdated
Show resolved
Hide resolved
This comment was marked as resolved.
This comment was marked as resolved.
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.
Done review pass
} | ||
} | ||
|
||
MethodSymbol? tryGetToArrayMethod(TypeSymbol spreadTypeOriginalDefinition, WellKnownType wellKnownType, WellKnownMember wellKnownMember) |
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 isn't really a try-get in traditional form, and it feels like the code that uses it is a bit convoluted because of it. Consider refactoring to be a standard tryGet
with an out
parameter.
@@ -599,6 +636,20 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A | |||
_factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)), | |||
isRef: false, | |||
indexTemp.Type)); | |||
}, | |||
(sideEffects, arrayTemp, spreadElement, rewrittenSpreadOperand) => |
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.
Consider naming these delegate parameters, especially now that there are multiple of them.
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.
Also, please spell out the types of these parameters. Same goes for the other similar delegates below.
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 wasn't sure what you meant by name the delegate parameters. Were you suggesting that I declare a named delegate type which has these parameter names?
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.
No, I mean specify the parameter name for each of these delegate arguments. What is the parameter this delegate is being passed to?
/// Returns true if type is convertible to Span or ReadOnlySpan. | ||
/// If non-identity conversion, also returns a non-null asSpanMethod. | ||
/// </summary> | ||
private bool TryGetSpanConversion(TypeSymbol type, out MethodSymbol? asSpanMethod) |
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.
Let's rearrange this method to simplify things. First, check to see if it's the array case. The rest of the cases depend on it being a named type symbol, so you can do that check once and bail if false, rather than type-checking against NamedTypeSymbol 3 times.
return false; | ||
} | ||
|
||
private BoundExpression? TryConvertToSpanOrReadOnlySpan(BoundExpression expression) |
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 feels like this is another method that would be better served by following the standard TryGet
pattern.
&& _factory.WellKnownMethod(getLengthMember, isOptional: true) is { } getLengthMethod | ||
&& _factory.WellKnownMethod(copyToMember, isOptional: true) is { } copyToMethod) | ||
{ | ||
return (getLengthMethod!, copyToMethod!); |
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.
Why do we need suppressions on this line?
if (type is ArrayTypeSymbol { IsSZArray: true } arrayType | ||
&& _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) | ||
{ | ||
asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); |
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.
What if the element type is something that can't be a valid type parameter, like a pointer? Do we have a test covering that?
if (type is ArrayTypeSymbol { IsSZArray: true } arrayType | ||
&& _factory.WellKnownMethod(WellKnownMember.System_Span_T__ctor_Array, isOptional: true) is { } spanCtorArray) | ||
{ | ||
asSpanMethod = spanCtorArray.AsMember(spanCtorArray.ContainingType.Construct(arrayType.ElementType)); |
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.
How do we know the element type is always going to be a valid type argument? Can we assert something?
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 would be fine with asserting, for example, that element type is implicitly convertible to object.
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 was a faulty assumption. The conversion to object is now checked in retail builds and if it isn't what we expect then we don't use either the ToArray or CopyTo optimization paths.
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.
Do we have a test that hits this? (It looks like the existing tests were unchanged, other than setting the target framework.)
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 new tests require the object conversion check in order to work properly.
@@ -599,6 +637,19 @@ private BoundExpression CreateAndPopulateArray(BoundCollectionExpression node, A | |||
_factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)), | |||
isRef: false, | |||
indexTemp.Type)); | |||
}, | |||
(ArrayBuilder<BoundExpression> sideEffects, BoundExpression arrayTemp, BoundCollectionExpressionSpreadElement spreadElement, BoundExpression rewrittenSpreadOperand) => |
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.
Still missing tryOptimizeSpreadElement:
naming.
Closes #70656
Closes #69988
Closes #69626 (I didn't see which types expose a well-known
AddRange(ReadOnlySpan)
though.)[..expr]
toexpr.ToArray()
when possible.List<T>
, usingList<T>.ToArray()
[elems, ..expr]
to useexpr.CopyTo(dest)
when possible.dest
toSpan<T>
expr
toSpan<T>
orReadOnlySpan<T>
[elems, ..expr]
todest.AddRange(expr)
forList<T>
when CollectionsMarshal is not availableexpr
toIEnumerable<T>
(only if it is an implicit reference conversion)Perf data for these various strategies can be found here:
(for arrays) #70656 (comment)
(for lists) #70656 (comment)