-
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 expressions: use inline array for ReadOnlySpan<T> argument to builder method #69227
Changes from all commits
d75c020
56afdf6
c5002c0
b47e209
aefb966
4cfe4e1
75ead79
d36040b
99717d6
d4c2081
c419b51
60e248e
adae5cd
6fc3312
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,26 +18,35 @@ internal sealed partial class LocalRewriter | |
Debug.Assert(!_inExpressionLambda); | ||
Debug.Assert(node.Type is { }); | ||
|
||
var collectionTypeKind = ConversionsBase.GetCollectionExpressionTypeKind(_compilation, node.Type, out var elementType); | ||
switch (collectionTypeKind) | ||
var previousSyntax = _factory.Syntax; | ||
_factory.Syntax = node.Syntax; | ||
try | ||
{ | ||
case CollectionExpressionTypeKind.CollectionInitializer: | ||
return VisitCollectionInitializerCollectionExpression(node, node.Type); | ||
case CollectionExpressionTypeKind.Array: | ||
case CollectionExpressionTypeKind.Span: | ||
case CollectionExpressionTypeKind.ReadOnlySpan: | ||
Debug.Assert(elementType is { }); | ||
return VisitArrayOrSpanCollectionExpression(node, node.Type, elementType); | ||
case CollectionExpressionTypeKind.CollectionBuilder: | ||
return VisitCollectionBuilderCollectionExpression(node); | ||
case CollectionExpressionTypeKind.ListInterface: | ||
return VisitListInterfaceCollectionExpression(node); | ||
default: | ||
throw ExceptionUtilities.UnexpectedValue(collectionTypeKind); | ||
var collectionTypeKind = ConversionsBase.GetCollectionExpressionTypeKind(_compilation, node.Type, out var elementType); | ||
switch (collectionTypeKind) | ||
{ | ||
case CollectionExpressionTypeKind.CollectionInitializer: | ||
return VisitCollectionInitializerCollectionExpression(node, node.Type); | ||
case CollectionExpressionTypeKind.Array: | ||
case CollectionExpressionTypeKind.Span: | ||
case CollectionExpressionTypeKind.ReadOnlySpan: | ||
Debug.Assert(elementType is { }); | ||
return VisitArrayOrSpanCollectionExpression(node, node.Type, TypeWithAnnotations.Create(elementType)); | ||
case CollectionExpressionTypeKind.CollectionBuilder: | ||
return VisitCollectionBuilderCollectionExpression(node); | ||
case CollectionExpressionTypeKind.ListInterface: | ||
return VisitListInterfaceCollectionExpression(node); | ||
default: | ||
throw ExceptionUtilities.UnexpectedValue(collectionTypeKind); | ||
} | ||
} | ||
finally | ||
{ | ||
_factory.Syntax = previousSyntax; | ||
} | ||
} | ||
|
||
private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType, TypeSymbol elementType) | ||
private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpression node, TypeSymbol collectionType, TypeWithAnnotations elementType) | ||
{ | ||
Debug.Assert(!_inExpressionLambda); | ||
|
||
|
@@ -50,8 +59,8 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr | |
Debug.Assert(collectionType.Name is "Span" or "ReadOnlySpan"); | ||
// We're constructing a Span<T> or ReadOnlySpan<T> rather than T[]. | ||
var spanType = (NamedTypeSymbol)collectionType; | ||
Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type, TypeCompareKind.AllIgnoreOptions)); | ||
arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, TypeWithAnnotations.Create(elementType)); | ||
Debug.Assert(elementType.Equals(spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0], TypeCompareKind.AllIgnoreOptions)); | ||
arrayType = ArrayTypeSymbol.CreateSZArray(_compilation.Assembly, elementType); | ||
spanConstructor = ((MethodSymbol)_compilation.GetWellKnownTypeMember( | ||
collectionType.Name == "Span" ? WellKnownMember.System_Span_T__ctor_Array : WellKnownMember.System_ReadOnlySpan_T__ctor_Array)!).AsMember(spanType); | ||
} | ||
|
@@ -64,7 +73,7 @@ private BoundExpression VisitArrayOrSpanCollectionExpression(BoundCollectionExpr | |
// The array initializer includes at least one spread element, so we'll create an intermediate List<T> instance. | ||
// https://github.com/dotnet/roslyn/issues/68785: Avoid intermediate List<T> if all spread elements have Length property. | ||
// https://github.com/dotnet/roslyn/issues/68785: Emit Enumerable.TryGetNonEnumeratedCount() and avoid intermediate List<T> at runtime. | ||
var listType = _compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T).Construct(elementType); | ||
var listType = _compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_List_T).Construct(ImmutableArray.Create(elementType)); | ||
var listToArray = ((MethodSymbol)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ToArray)!).AsMember(listType); | ||
var list = VisitCollectionInitializerCollectionExpression(node, collectionType); | ||
array = _factory.Call(list, listToArray); | ||
|
@@ -159,16 +168,35 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti | |
Debug.Assert(!_inExpressionLambda); | ||
Debug.Assert(node.Type is { }); | ||
|
||
var syntax = node.Syntax; | ||
var elements = node.Elements; | ||
var constructMethod = node.CollectionBuilderMethod; | ||
|
||
Debug.Assert(constructMethod is { }); | ||
Debug.Assert(constructMethod.ReturnType.Equals(node.Type, TypeCompareKind.AllIgnoreOptions)); | ||
|
||
var spanType = (NamedTypeSymbol)constructMethod.Parameters[0].Type; | ||
Debug.Assert(spanType.OriginalDefinition.Equals(_compilation.GetWellKnownType(WellKnownType.System_ReadOnlySpan_T), TypeCompareKind.AllIgnoreOptions)); | ||
|
||
var span = VisitArrayOrSpanCollectionExpression(node, spanType, spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0].Type); | ||
return new BoundCall( | ||
node.Syntax, | ||
var elementType = spanType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0]; | ||
var locals = ArrayBuilder<LocalSymbol>.GetInstance(); | ||
var sideEffects = ArrayBuilder<BoundExpression>.GetInstance(); | ||
BoundExpression span; | ||
|
||
if (elements.Length > 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be any kind of upper limit/heuristic? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now, we don't think there should be a limit (see notes), because the elements are already explicit in the source and because the work around to use the heap is to use C# 11 syntax. |
||
&& !elements.Any(i => i is BoundCollectionExpressionSpreadElement) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am a bit worried, from a usability perspective, about the potential for a perf cliff if you include a spread, and whether there's going to be advice of "yeah, for perf you shouldn't use spreads". Think we may want to push on the "Give me a span, on the stack if possible" API for allowing us to make this more of a gradient. |
||
&& _compilation.Assembly.RuntimeSupportsInlineArrayTypes | ||
&& (!constructMethod.ReturnType.IsRefLikeType || constructMethod.Parameters[0].EffectiveScope == ScopedKind.ScopedValue)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did we add tests to cover those conditions, or were those getting hit by existing scenarios? #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Covered by |
||
{ | ||
span = CreateAndPopulateInlineArray(syntax, elementType, elements, locals, sideEffects); | ||
} | ||
else | ||
{ | ||
span = VisitArrayOrSpanCollectionExpression(node, spanType, elementType); | ||
} | ||
|
||
var call = new BoundCall( | ||
syntax, | ||
receiverOpt: null, | ||
method: constructMethod, | ||
arguments: ImmutableArray.Create(span), | ||
|
@@ -181,6 +209,64 @@ private BoundExpression VisitCollectionBuilderCollectionExpression(BoundCollecti | |
defaultArguments: default, | ||
resultKind: LookupResultKind.Viable, | ||
type: constructMethod.ReturnType); | ||
|
||
return new BoundSequence( | ||
syntax, | ||
locals.ToImmutableAndFree(), | ||
sideEffects.ToImmutableAndFree(), | ||
call, | ||
call.Type); | ||
} | ||
|
||
private BoundExpression CreateAndPopulateInlineArray( | ||
SyntaxNode syntax, | ||
TypeWithAnnotations elementType, | ||
ImmutableArray<BoundExpression> elements, | ||
ArrayBuilder<LocalSymbol> locals, | ||
ArrayBuilder<BoundExpression> sideEffects) | ||
{ | ||
Debug.Assert(elements.Length > 0); | ||
Debug.Assert(_factory.ModuleBuilderOpt is { }); | ||
Debug.Assert(_diagnostics.DiagnosticBag is { }); | ||
Debug.Assert(_compilation.Assembly.RuntimeSupportsInlineArrayTypes); | ||
|
||
int arrayLength = elements.Length; | ||
var inlineArrayType = _factory.ModuleBuilderOpt.EnsureInlineArrayTypeExists(syntax, _factory, arrayLength, _diagnostics.DiagnosticBag).Construct(ImmutableArray.Create(elementType)); | ||
Debug.Assert(inlineArrayType.HasInlineArrayAttribute(out int inlineArrayLength) && inlineArrayLength == arrayLength); | ||
|
||
var intType = _factory.SpecialType(SpecialType.System_Int32); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test where type There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. I believe we're handling that correctly here, and I don't think we'd get this far without an error in that case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have tests for various other missing types, but not this one. Let's cover it too. |
||
MethodSymbol elementRef = _factory.ModuleBuilderOpt.EnsureInlineArrayElementRefExists(syntax, intType, _diagnostics.DiagnosticBag). | ||
Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType)); | ||
|
||
// Create an inline array and assign to a local. | ||
// var tmp = new <>y__InlineArrayN<ElementType>(); | ||
BoundAssignmentOperator assignmentToTemp; | ||
BoundLocal inlineArrayLocal = _factory.StoreToTemp(new BoundDefaultExpression(syntax, inlineArrayType), out assignmentToTemp, isKnownToReferToTempIfReferenceType: true); | ||
sideEffects.Add(assignmentToTemp); | ||
locals.Add(inlineArrayLocal.LocalSymbol); | ||
|
||
// Populate the inline array. | ||
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 0) = element0; | ||
// InlineArrayElementRef<<>y__InlineArrayN<ElementType>, ElementType>(ref tmp, 1) = element1; | ||
// ... | ||
for (int i = 0; i < arrayLength; i++) | ||
{ | ||
var element = VisitExpression(elements[i]); | ||
var call = _factory.Call(null, elementRef, inlineArrayLocal, _factory.Literal(i), useStrictArgumentRefKinds: true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add pseudo-code comments corresponding to what is generated #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for adding. It makes things much easier to follow :-) |
||
var assignment = new BoundAssignmentOperator(syntax, call, element, type: call.Type) { WasCompilerGenerated = true }; | ||
sideEffects.Add(assignment); | ||
} | ||
|
||
// Get a span to the inline array. | ||
// ... InlineArrayAsReadOnlySpan<<>y__InlineArrayN<ElementType>, ElementType>(in tmp, N) | ||
var inlineArrayAsReadOnlySpan = _factory.ModuleBuilderOpt.EnsureInlineArrayAsReadOnlySpanExists(syntax, _factory.WellKnownType(WellKnownType.System_ReadOnlySpan_T), intType, _diagnostics.DiagnosticBag). | ||
Construct(ImmutableArray.Create(TypeWithAnnotations.Create(inlineArrayType), elementType)); | ||
return _factory.Call( | ||
receiver: null, | ||
inlineArrayAsReadOnlySpan, | ||
inlineArrayLocal, | ||
_factory.Literal(arrayLength), | ||
useStrictArgumentRefKinds: true); | ||
} | ||
|
||
private BoundExpression MakeCollectionExpressionSpreadElement(BoundCollectionExpressionSpreadElement initializer) | ||
|
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 with two collection expressions of the same size? (the InlineArray type will be re-used/shared) #Closed
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.
CollectionBuilder_InlineArrayTypes